From 5087f5d3df8955e8420f00f8a47cfc841ba87062 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 9 Apr 2019 22:38:50 +0200 Subject: [PATCH 1/8] Added support for new asio api --- asio_compatibility.hpp | 91 ++++++++++++++++++++++++++++++++++++++++++ client_http.hpp | 57 ++++++++++---------------- client_https.hpp | 4 +- server_http.hpp | 53 ++++++++++-------------- tests/io_test.cpp | 12 ++---- tests/parse_test.cpp | 4 +- 6 files changed, 142 insertions(+), 79 deletions(-) create mode 100644 asio_compatibility.hpp diff --git a/asio_compatibility.hpp b/asio_compatibility.hpp new file mode 100644 index 0000000..6e932fd --- /dev/null +++ b/asio_compatibility.hpp @@ -0,0 +1,91 @@ +#ifndef SIMPLE_WEB_ASIO_HPP +#define SIMPLE_WEB_ASIO_HPP + +#include + +#ifdef USE_STANDALONE_ASIO +#include +#include +namespace SimpleWeb { + using error_code = std::error_code; + using errc = std::errc; + using system_error = std::system_error; + namespace make_error_code = std; +} // namespace SimpleWeb +#else +#include +#include +namespace SimpleWeb { + namespace asio = boost::asio; + using error_code = boost::system::error_code; + namespace errc = boost::system::errc; + using system_error = boost::system::system_error; + namespace make_error_code = boost::system::errc; +} // namespace SimpleWeb +#endif + +namespace SimpleWeb { +#if(USE_STANDALONE_ASIO && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 // TODO: change to 101300 + using io_context = asio::io_context; + using resolver_results = asio::ip::tcp::resolver::results_type; + using async_connect_endpoint = asio::ip::tcp::endpoint; + + inline void restart(io_context &context) noexcept { + context.restart(); + } + inline void timer_expires_after(asio::steady_timer &timer, const asio::steady_timer::duration &duration) { + timer.expires_after(duration); + } + inline asio::ip::address make_address(const std::string &str) noexcept { + return asio::ip::make_address(str); + } + template + io_context &get_socket_context(socket_type &socket) { + return socket.get_executor().context(); + } + template + void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair &host_port, handler_type &&handler) { + resolver.async_resolve(host_port.first, host_port.second, handler); + } + template + void post(executor_type &executor, handler_type &&handler) { + asio::post(executor, handler); + } + template + asio::executor_binder::type, executor_type> bind_executor(executor_type &executor, handler_type &&handler) { + return asio::bind_executor(executor, handler); + } +#else + using io_context = asio::io_service; + using resolver_results = asio::ip::tcp::resolver::iterator; + using async_connect_endpoint = asio::ip::tcp::resolver::iterator; + + inline void restart(io_context &context) noexcept { + context.reset(); + } + inline void timer_expires_after(asio::steady_timer &timer, const asio::steady_timer::duration &duration) { + timer.expires_from_now(duration); + } + inline asio::ip::address make_address(const std::string &str) noexcept { + return asio::ip::address::from_string(str); + } + template + io_context &get_socket_context(socket_type &socket) { + return socket.get_io_service(); + } + template + void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair &host_port, handler_type &&handler) { + resolver.async_resolve(asio::ip::tcp::resolver::query(host_port.first, host_port.second), handler); + } + template + void post(executor_type &executor, handler_type &&handler) { + executor.post(handler); + } + template + asio::detail::wrapped_handler bind_executor(executor_type &executor, handler_type &&handler) { + return executor.wrap(handler); + } +#endif +} // namespace SimpleWeb + +#endif /* SIMPLE_WEB_ASIO_HPP */ diff --git a/client_http.hpp b/client_http.hpp index 4c8b085..532993c 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -1,6 +1,7 @@ #ifndef CLIENT_HTTP_HPP #define CLIENT_HTTP_HPP +#include "asio_compatibility.hpp" #include "utility.hpp" #include #include @@ -8,27 +9,6 @@ #include #include -#ifdef USE_STANDALONE_ASIO -#include -#include -namespace SimpleWeb { - using error_code = std::error_code; - using errc = std::errc; - using system_error = std::system_error; - namespace make_error_code = std; -} // namespace SimpleWeb -#else -#include -#include -namespace SimpleWeb { - namespace asio = boost::asio; - using error_code = boost::system::error_code; - namespace errc = boost::system::errc; - using system_error = boost::system::system_error; - namespace make_error_code = boost::system::errc; -} // namespace SimpleWeb -#endif - namespace SimpleWeb { template class Client; @@ -119,8 +99,12 @@ namespace SimpleWeb { timer = nullptr; return; } - timer = std::unique_ptr(new asio::steady_timer(socket->get_io_service())); - timer->expires_from_now(std::chrono::seconds(seconds)); + timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket))); + try { + timer_expires_after(*timer, std::chrono::seconds(seconds)); + } + catch(...) { + } auto self = this->shared_from_this(); timer->async_wait([self](const error_code &ec) { if(!ec) { @@ -132,8 +116,11 @@ namespace SimpleWeb { void cancel_timeout() noexcept { if(timer) { - error_code ec; - timer->cancel(ec); + try { + timer->cancel(); + } + catch(...) { + } } } }; @@ -155,7 +142,7 @@ namespace SimpleWeb { /// If you have your own asio::io_service, store its pointer here before calling request(). /// When using asynchronous requests, running the io_service is up to the programmer. - std::shared_ptr io_service; + std::shared_ptr io_service; /// Convenience function to perform synchronous request. The io_service is run within this function. /// If reusing the io_service for other tasks, use the asynchronous request functions instead. @@ -178,7 +165,7 @@ namespace SimpleWeb { std::lock_guard lock(concurrent_synchronous_requests_mutex); --concurrent_synchronous_requests; if(!concurrent_synchronous_requests) - io_service->reset(); + restart(*io_service); } if(ec) @@ -208,7 +195,7 @@ namespace SimpleWeb { std::lock_guard lock(concurrent_synchronous_requests_mutex); --concurrent_synchronous_requests; if(!concurrent_synchronous_requests) - io_service->reset(); + restart(*io_service); } if(ec) @@ -362,7 +349,7 @@ namespace SimpleWeb { unsigned short port; unsigned short default_port; - std::unique_ptr query; + std::unique_ptr> host_port; std::unordered_set> connections; std::mutex connections_mutex; @@ -383,7 +370,7 @@ namespace SimpleWeb { std::lock_guard lock(connections_mutex); if(!io_service) { - io_service = std::make_shared(); + io_service = std::make_shared(); internal_io_service = true; } @@ -400,12 +387,12 @@ namespace SimpleWeb { connection->attempt_reconnect = true; connection->in_use = true; - if(!query) { + if(!host_port) { if(config.proxy_server.empty()) - query = std::unique_ptr(new asio::ip::tcp::resolver::query(host, std::to_string(port))); + host_port = std::unique_ptr>(new std::pair(host, std::to_string(port))); else { auto proxy_host_port = parse_host_port(config.proxy_server, 8080); - query = std::unique_ptr(new asio::ip::tcp::resolver::query(proxy_host_port.first, std::to_string(proxy_host_port.second))); + host_port = std::unique_ptr>(new std::pair(proxy_host_port.first, std::to_string(proxy_host_port.second))); } } @@ -656,14 +643,14 @@ namespace SimpleWeb { if(!session->connection->socket->lowest_layer().is_open()) { auto resolver = std::make_shared(*io_service); session->connection->set_timeout(config.timeout_connect); - resolver->async_resolve(*query, [this, session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) { + async_resolve(*resolver, *host_port, [this, session, resolver](const error_code &ec, resolver_results results) { session->connection->cancel_timeout(); auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; if(!ec) { session->connection->set_timeout(config.timeout_connect); - asio::async_connect(*session->connection->socket, it, [this, session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) { + asio::async_connect(*session->connection->socket, results, [this, session, resolver](const error_code &ec, async_connect_endpoint /*endpoint*/) { session->connection->cancel_timeout(); auto lock = session->connection->handler_runner->continue_lock(); if(!lock) diff --git a/client_https.hpp b/client_https.hpp index a5f9f7c..09eecf5 100644 --- a/client_https.hpp +++ b/client_https.hpp @@ -47,13 +47,13 @@ namespace SimpleWeb { void connect(const std::shared_ptr &session) override { if(!session->connection->socket->lowest_layer().is_open()) { auto resolver = std::make_shared(*io_service); - resolver->async_resolve(*query, [this, session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) { + async_resolve(*resolver, *host_port, [this, session, resolver](const error_code &ec, resolver_results results) { auto lock = session->connection->handler_runner->continue_lock(); if(!lock) return; if(!ec) { session->connection->set_timeout(this->config.timeout_connect); - asio::async_connect(session->connection->socket->lowest_layer(), it, [this, session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) { + asio::async_connect(session->connection->socket->lowest_layer(), results, [this, session, resolver](const error_code &ec, async_connect_endpoint /*endpoint*/) { session->connection->cancel_timeout(); auto lock = session->connection->handler_runner->continue_lock(); if(!lock) diff --git a/server_http.hpp b/server_http.hpp index 59ec711..c092874 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -1,6 +1,7 @@ #ifndef SERVER_HTTP_HPP #define SERVER_HTTP_HPP +#include "asio_compatibility.hpp" #include "utility.hpp" #include #include @@ -12,25 +13,6 @@ #include #include -#ifdef USE_STANDALONE_ASIO -#include -#include -namespace SimpleWeb { - using error_code = std::error_code; - using errc = std::errc; - namespace make_error_code = std; -} // namespace SimpleWeb -#else -#include -#include -namespace SimpleWeb { - namespace asio = boost::asio; - using error_code = boost::system::error_code; - namespace errc = boost::system::errc; - namespace make_error_code = boost::system::errc; -} // namespace SimpleWeb -#endif - // Late 2017 TODO: remove the following checks and always use std::regex #ifdef USE_BOOST_REGEX #include @@ -63,10 +45,10 @@ namespace SimpleWeb { std::shared_ptr session; long timeout_content; - asio::io_service::strand strand; + io_context::strand strand; std::list, std::function>> send_queue; - Response(std::shared_ptr session_, long timeout_content) noexcept : std::ostream(nullptr), session(std::move(session_)), timeout_content(timeout_content), strand(session->connection->socket->get_io_service()) { + Response(std::shared_ptr session_, long timeout_content) noexcept : std::ostream(nullptr), session(std::move(session_)), timeout_content(timeout_content), strand(get_socket_context(*session->connection->socket)) { rdbuf(streambuf.get()); } @@ -90,8 +72,8 @@ namespace SimpleWeb { void send_from_queue() { auto self = this->shared_from_this(); - strand.post([self]() { - asio::async_write(*self->session->connection->socket, *self->send_queue.begin()->first, self->strand.wrap([self](const error_code &ec, std::size_t /*bytes_transferred*/) { + SimpleWeb::post(strand, [self]() { + asio::async_write(*self->session->connection->socket, *self->send_queue.begin()->first, SimpleWeb::bind_executor(self->strand, [self](const error_code &ec, std::size_t /*bytes_transferred*/) { auto lock = self->session->connection->handler_runner->continue_lock(); if(!lock) return; @@ -142,7 +124,7 @@ namespace SimpleWeb { rdbuf(this->streambuf.get()); auto self = this->shared_from_this(); - strand.post([self, streambuf, callback]() { + SimpleWeb::post(strand, [self, streambuf, callback]() { self->send_queue.emplace_back(streambuf, callback); if(self->send_queue.size() == 1) self->send_from_queue(); @@ -296,8 +278,12 @@ namespace SimpleWeb { return; } - timer = std::unique_ptr(new asio::steady_timer(socket->get_io_service())); - timer->expires_from_now(std::chrono::seconds(seconds)); + timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket))); + try { + timer_expires_after(*timer, std::chrono::seconds(seconds)); + } + catch(...) { + } auto self = this->shared_from_this(); timer->async_wait([self](const error_code &ec) { if(!ec) @@ -307,8 +293,11 @@ namespace SimpleWeb { void cancel_timeout() noexcept { if(timer) { - error_code ec; - timer->cancel(ec); + try { + timer->cancel(); + } + catch(...) { + } } } }; @@ -380,7 +369,7 @@ namespace SimpleWeb { std::function &, std::shared_ptr::Request>)> on_upgrade; /// If you have your own asio::io_service, store its pointer here before running start(). - std::shared_ptr io_service; + std::shared_ptr io_service; /// If you know the server port in advance, use start() instead. /// Returns assigned port. If io_service is not set, an internal io_service is created instead. @@ -388,12 +377,12 @@ namespace SimpleWeb { unsigned short bind() { asio::ip::tcp::endpoint endpoint; if(config.address.size() > 0) - endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port); + endpoint = asio::ip::tcp::endpoint(make_address(config.address), config.port); else endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v6(), config.port); if(!io_service) { - io_service = std::make_shared(); + io_service = std::make_shared(); internal_io_service = true; } @@ -424,7 +413,7 @@ namespace SimpleWeb { if(internal_io_service) { if(io_service->stopped()) - io_service->reset(); + restart(*io_service); // If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling threads.clear(); diff --git a/tests/io_test.cpp b/tests/io_test.cpp index d15270b..712481b 100644 --- a/tests/io_test.cpp +++ b/tests/io_test.cpp @@ -4,10 +4,6 @@ using namespace std; -#ifndef USE_STANDALONE_ASIO -namespace asio = boost::asio; -#endif - using HttpServer = SimpleWeb::Server; using HttpClient = SimpleWeb::Client; @@ -315,7 +311,7 @@ int main() { for(auto &thread : threads) thread.join(); ASSERT(client.connections.size() == 100); - client.io_service->reset(); + SimpleWeb::restart(*client.io_service); client.io_service->run(); ASSERT(client.connections.size() == 1); for(auto call : calls) @@ -390,7 +386,7 @@ int main() { // Test Client client's stop() for(size_t c = 0; c < 40; ++c) { - auto io_service = make_shared(); + auto io_service = make_shared(); bool call = false; HttpClient client("localhost:8080"); client.io_service = io_service; @@ -410,7 +406,7 @@ int main() { // Test Client destructor that should cancel the client's request for(size_t c = 0; c < 40; ++c) { - auto io_service = make_shared(); + auto io_service = make_shared(); { HttpClient client("localhost:8080"); client.io_service = io_service; @@ -431,7 +427,7 @@ int main() { // Test server destructor { - auto io_service = make_shared(); + auto io_service = make_shared(); bool call = false; bool client_catch = false; { diff --git a/tests/parse_test.cpp b/tests/parse_test.cpp index c521b38..e739a59 100644 --- a/tests/parse_test.cpp +++ b/tests/parse_test.cpp @@ -147,7 +147,7 @@ int main() { ASSERT(fields_result1 == fields_result2 && fields_result1 == fields); auto serverTest = make_shared(); - serverTest->io_service = std::make_shared(); + serverTest->io_service = std::make_shared(); serverTest->parse_request_test(); @@ -160,7 +160,7 @@ int main() { clientTest2->parse_response_header_test(); - asio::io_service io_service; + io_context io_service; asio::ip::tcp::socket socket(io_service); SimpleWeb::Server::Request request(static_cast(-1), nullptr); { From 44c2f733dcf535044e602740ac7d8a6b6ef90e7e Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 20 May 2019 18:39:54 +0200 Subject: [PATCH 2/8] New asio api: removed unnecessary SimpleWeb::timer_expires_after --- asio_compatibility.hpp | 8 +------- client_http.hpp | 7 +------ server_http.hpp | 7 +------ 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/asio_compatibility.hpp b/asio_compatibility.hpp index 6e932fd..759c595 100644 --- a/asio_compatibility.hpp +++ b/asio_compatibility.hpp @@ -25,7 +25,7 @@ namespace SimpleWeb { #endif namespace SimpleWeb { -#if(USE_STANDALONE_ASIO && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 // TODO: change to 101300 +#if(USE_STANDALONE_ASIO && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300 using io_context = asio::io_context; using resolver_results = asio::ip::tcp::resolver::results_type; using async_connect_endpoint = asio::ip::tcp::endpoint; @@ -33,9 +33,6 @@ namespace SimpleWeb { inline void restart(io_context &context) noexcept { context.restart(); } - inline void timer_expires_after(asio::steady_timer &timer, const asio::steady_timer::duration &duration) { - timer.expires_after(duration); - } inline asio::ip::address make_address(const std::string &str) noexcept { return asio::ip::make_address(str); } @@ -63,9 +60,6 @@ namespace SimpleWeb { inline void restart(io_context &context) noexcept { context.reset(); } - inline void timer_expires_after(asio::steady_timer &timer, const asio::steady_timer::duration &duration) { - timer.expires_from_now(duration); - } inline asio::ip::address make_address(const std::string &str) noexcept { return asio::ip::address::from_string(str); } diff --git a/client_http.hpp b/client_http.hpp index 532993c..bb2205a 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -99,12 +99,7 @@ namespace SimpleWeb { timer = nullptr; return; } - timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket))); - try { - timer_expires_after(*timer, std::chrono::seconds(seconds)); - } - catch(...) { - } + timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket), std::chrono::seconds(seconds))); auto self = this->shared_from_this(); timer->async_wait([self](const error_code &ec) { if(!ec) { diff --git a/server_http.hpp b/server_http.hpp index c092874..01d4835 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -278,12 +278,7 @@ namespace SimpleWeb { return; } - timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket))); - try { - timer_expires_after(*timer, std::chrono::seconds(seconds)); - } - catch(...) { - } + timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket), std::chrono::seconds(seconds))); auto self = this->shared_from_this(); timer->async_wait([self](const error_code &ec) { if(!ec) From 1b5f0626788ca529564375b2687d4c288dfc2743 Mon Sep 17 00:00:00 2001 From: eidheim <=> Date: Wed, 22 May 2019 11:50:18 +0000 Subject: [PATCH 3/8] New asio: removed use of strand, and fixed steady_timer constructor call --- asio_compatibility.hpp | 14 +++------- client_http.hpp | 2 +- server_http.hpp | 58 ++++++++++++++++++++---------------------- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/asio_compatibility.hpp b/asio_compatibility.hpp index 759c595..c864900 100644 --- a/asio_compatibility.hpp +++ b/asio_compatibility.hpp @@ -37,8 +37,8 @@ namespace SimpleWeb { return asio::ip::make_address(str); } template - io_context &get_socket_context(socket_type &socket) { - return socket.get_executor().context(); + asio::executor get_socket_executor(socket_type &socket) { + return socket.get_executor(); } template void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair &host_port, handler_type &&handler) { @@ -48,10 +48,6 @@ namespace SimpleWeb { void post(executor_type &executor, handler_type &&handler) { asio::post(executor, handler); } - template - asio::executor_binder::type, executor_type> bind_executor(executor_type &executor, handler_type &&handler) { - return asio::bind_executor(executor, handler); - } #else using io_context = asio::io_service; using resolver_results = asio::ip::tcp::resolver::iterator; @@ -64,7 +60,7 @@ namespace SimpleWeb { return asio::ip::address::from_string(str); } template - io_context &get_socket_context(socket_type &socket) { + io_context &get_socket_executor(socket_type &socket) { return socket.get_io_service(); } template @@ -75,10 +71,6 @@ namespace SimpleWeb { void post(executor_type &executor, handler_type &&handler) { executor.post(handler); } - template - asio::detail::wrapped_handler bind_executor(executor_type &executor, handler_type &&handler) { - return executor.wrap(handler); - } #endif } // namespace SimpleWeb diff --git a/client_http.hpp b/client_http.hpp index bb2205a..fd2471f 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -99,7 +99,7 @@ namespace SimpleWeb { timer = nullptr; return; } - timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket), std::chrono::seconds(seconds))); + timer = std::unique_ptr(new asio::steady_timer(get_socket_executor(*socket), std::chrono::seconds(seconds))); auto self = this->shared_from_this(); timer->async_wait([self](const error_code &ec) { if(!ec) { diff --git a/server_http.hpp b/server_http.hpp index 01d4835..d86e5c7 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -45,10 +45,10 @@ namespace SimpleWeb { std::shared_ptr session; long timeout_content; - io_context::strand strand; + std::mutex send_queue_mutex; std::list, std::function>> send_queue; - Response(std::shared_ptr session_, long timeout_content) noexcept : std::ostream(nullptr), session(std::move(session_)), timeout_content(timeout_content), strand(get_socket_context(*session->connection->socket)) { + Response(std::shared_ptr session_, long timeout_content) noexcept : std::ostream(nullptr), session(std::move(session_)), timeout_content(timeout_content) { rdbuf(streambuf.get()); } @@ -70,30 +70,30 @@ namespace SimpleWeb { *this << "\r\n"; } + /// send_queue_mutex must be locked here void send_from_queue() { auto self = this->shared_from_this(); - SimpleWeb::post(strand, [self]() { - asio::async_write(*self->session->connection->socket, *self->send_queue.begin()->first, SimpleWeb::bind_executor(self->strand, [self](const error_code &ec, std::size_t /*bytes_transferred*/) { - auto lock = self->session->connection->handler_runner->continue_lock(); - if(!lock) - return; - if(!ec) { - auto it = self->send_queue.begin(); - if(it->second) - it->second(ec); - self->send_queue.erase(it); - if(self->send_queue.size() > 0) - self->send_from_queue(); + asio::async_write(*self->session->connection->socket, *send_queue.begin()->first, [self](const error_code &ec, std::size_t /*bytes_transferred*/) { + auto lock = self->session->connection->handler_runner->continue_lock(); + if(!lock) + return; + std::lock_guard send_queue_lock(self->send_queue_mutex); + if(!ec) { + auto it = self->send_queue.begin(); + if(it->second) + it->second(ec); + self->send_queue.erase(it); + if(self->send_queue.size() > 0) + self->send_from_queue(); + } + else { + // All handlers in the queue is called with ec: + for(auto &pair : self->send_queue) { + if(pair.second) + pair.second(ec); } - else { - // All handlers in the queue is called with ec: - for(auto &pair : self->send_queue) { - if(pair.second) - pair.second(ec); - } - self->send_queue.clear(); - } - })); + self->send_queue.clear(); + } }); } @@ -123,12 +123,10 @@ namespace SimpleWeb { this->streambuf = std::unique_ptr(new asio::streambuf()); rdbuf(this->streambuf.get()); - auto self = this->shared_from_this(); - SimpleWeb::post(strand, [self, streambuf, callback]() { - self->send_queue.emplace_back(streambuf, callback); - if(self->send_queue.size() == 1) - self->send_from_queue(); - }); + std::lock_guard lock(send_queue_mutex); + send_queue.emplace_back(streambuf, callback); + if(send_queue.size() == 1) + send_from_queue(); } /// Write directly to stream buffer using std::ostream::write @@ -278,7 +276,7 @@ namespace SimpleWeb { return; } - timer = std::unique_ptr(new asio::steady_timer(get_socket_context(*socket), std::chrono::seconds(seconds))); + timer = std::unique_ptr(new asio::steady_timer(get_socket_executor(*socket), std::chrono::seconds(seconds))); auto self = this->shared_from_this(); timer->async_wait([self](const error_code &ec) { if(!ec) From 514a135e0cf95d606a6eaa006f1866dd4438eab8 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 27 May 2019 21:13:28 +0200 Subject: [PATCH 4/8] Improved set_timeout(): no longer keeps connection alive longer than necessary. --- client_http.hpp | 10 ++++++---- server_http.hpp | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/client_http.hpp b/client_http.hpp index fd2471f..5d1a1d0 100644 --- a/client_http.hpp +++ b/client_http.hpp @@ -100,11 +100,13 @@ namespace SimpleWeb { return; } timer = std::unique_ptr(new asio::steady_timer(get_socket_executor(*socket), std::chrono::seconds(seconds))); - auto self = this->shared_from_this(); - timer->async_wait([self](const error_code &ec) { + std::weak_ptr self_weak(this->shared_from_this()); // To avoid keeping Connection instance alive longer than needed + timer->async_wait([self_weak](const error_code &ec) { if(!ec) { - error_code ec; - self->socket->lowest_layer().cancel(ec); + if(auto self = self_weak.lock()) { + error_code ec; + self->socket->lowest_layer().cancel(ec); + } } }); } diff --git a/server_http.hpp b/server_http.hpp index d86e5c7..290d3f4 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -277,10 +277,12 @@ namespace SimpleWeb { } timer = std::unique_ptr(new asio::steady_timer(get_socket_executor(*socket), std::chrono::seconds(seconds))); - auto self = this->shared_from_this(); - timer->async_wait([self](const error_code &ec) { - if(!ec) - self->close(); + std::weak_ptr self_weak(this->shared_from_this()); // To avoid keeping Connection instance alive longer than needed + timer->async_wait([self_weak](const error_code &ec) { + if(!ec) { + if(auto self = self_weak.lock()) + self->close(); + } }); } From 9ca86827d976621ff9ff99d67a2f2a2f6381bb47 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 28 May 2019 13:07:20 +0200 Subject: [PATCH 5/8] Unlocks send_queue_lock before calling callbacks in case send_from_queue is invoked in callbacks --- server_http.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server_http.hpp b/server_http.hpp index 290d3f4..5b243e8 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -77,22 +77,30 @@ namespace SimpleWeb { auto lock = self->session->connection->handler_runner->continue_lock(); if(!lock) return; - std::lock_guard send_queue_lock(self->send_queue_mutex); + std::unique_lock send_queue_lock(self->send_queue_mutex); if(!ec) { auto it = self->send_queue.begin(); - if(it->second) - it->second(ec); + auto callback = std::move(it->second); self->send_queue.erase(it); if(self->send_queue.size() > 0) self->send_from_queue(); + + send_queue_lock.unlock(); + if(callback) + callback(ec); } else { // All handlers in the queue is called with ec: + std::vector> callbacks; for(auto &pair : self->send_queue) { if(pair.second) - pair.second(ec); + callbacks.emplace_back(std::move(pair.second)); } self->send_queue.clear(); + + send_queue_lock.unlock(); + for(auto &callback : callbacks) + callback(ec); } }); } From 045d4fce8dd6c100a148491c4273fb11a804bbb5 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 3 Jun 2019 10:17:38 +0200 Subject: [PATCH 6/8] Remove SimpleWeb::post and added std::forward to SimpleWeb::get_socket_executor --- asio_compatibility.hpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/asio_compatibility.hpp b/asio_compatibility.hpp index c864900..8b6c7ed 100644 --- a/asio_compatibility.hpp +++ b/asio_compatibility.hpp @@ -42,11 +42,7 @@ namespace SimpleWeb { } template void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair &host_port, handler_type &&handler) { - resolver.async_resolve(host_port.first, host_port.second, handler); - } - template - void post(executor_type &executor, handler_type &&handler) { - asio::post(executor, handler); + resolver.async_resolve(host_port.first, host_port.second, std::forward(handler)); } #else using io_context = asio::io_service; @@ -65,11 +61,7 @@ namespace SimpleWeb { } template void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair &host_port, handler_type &&handler) { - resolver.async_resolve(asio::ip::tcp::resolver::query(host_port.first, host_port.second), handler); - } - template - void post(executor_type &executor, handler_type &&handler) { - executor.post(handler); + resolver.async_resolve(asio::ip::tcp::resolver::query(host_port.first, host_port.second), std::forward(handler)); } #endif } // namespace SimpleWeb From 3fcf62865795251ab34bb33f185ba5407ecadbff Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 3 Jun 2019 12:12:33 +0200 Subject: [PATCH 7/8] Minor cleanup in send_from_queue --- server_http.hpp | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/server_http.hpp b/server_http.hpp index 5b243e8..2321964 100644 --- a/server_http.hpp +++ b/server_http.hpp @@ -77,30 +77,32 @@ namespace SimpleWeb { auto lock = self->session->connection->handler_runner->continue_lock(); if(!lock) return; - std::unique_lock send_queue_lock(self->send_queue_mutex); - if(!ec) { - auto it = self->send_queue.begin(); - auto callback = std::move(it->second); - self->send_queue.erase(it); - if(self->send_queue.size() > 0) - self->send_from_queue(); + { + std::unique_lock lock(self->send_queue_mutex); + if(!ec) { + auto it = self->send_queue.begin(); + auto callback = std::move(it->second); + self->send_queue.erase(it); + if(self->send_queue.size() > 0) + self->send_from_queue(); - send_queue_lock.unlock(); - if(callback) - callback(ec); - } - else { - // All handlers in the queue is called with ec: - std::vector> callbacks; - for(auto &pair : self->send_queue) { - if(pair.second) - callbacks.emplace_back(std::move(pair.second)); + lock.unlock(); + if(callback) + callback(ec); } - self->send_queue.clear(); + else { + // All handlers in the queue is called with ec: + std::vector> callbacks; + for(auto &pair : self->send_queue) { + if(pair.second) + callbacks.emplace_back(std::move(pair.second)); + } + self->send_queue.clear(); - send_queue_lock.unlock(); - for(auto &callback : callbacks) - callback(ec); + lock.unlock(); + for(auto &callback : callbacks) + callback(ec); + } } }); } From 1f699fc3047de8145869a8db6d44b1bd0023ef1e Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 13 Jun 2019 19:37:43 +0200 Subject: [PATCH 8/8] Related to #246 : added test where multiple clients sends posts concurrently to a server --- tests/io_test.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/io_test.cpp b/tests/io_test.cpp index 712481b..ebed11c 100644 --- a/tests/io_test.cpp +++ b/tests/io_test.cpp @@ -317,6 +317,27 @@ int main() { for(auto call : calls) ASSERT(call); } + + // Test concurrent requests from different clients + { + vector calls(10, 0); + vector threads; + for(size_t c = 0; c < 10; ++c) { + threads.emplace_back([c, &calls] { + HttpClient client("localhost:8080"); + client.request("POST", "/string", "A string", [c, &calls](shared_ptr response, const SimpleWeb::error_code &ec) { + ASSERT(!ec); + ASSERT(response->content.string() == "A string"); + calls[c] = 1; + }); + client.io_service->run(); + }); + } + for(auto &thread : threads) + thread.join(); + for(auto call : calls) + ASSERT(call); + } } // Test concurrent synchronous request calls