diff --git a/.travis.yml b/.travis.yml index 85433623..2dabe462 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: c++ -os: linux -dist: trusty +os: linux +dist: focal sudo: required matrix: @@ -15,14 +15,10 @@ matrix: env: RPCLIB_CXX_STANDARD=14 BUILD_TYPE=Release COVERAGE="OFF" before_install: - - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update -qq install: - - sudo apt-get install -qq g++-5 - - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 90 - - wget https://cmake.org/files/v3.9/cmake-3.9.2-Linux-x86_64.sh - - sudo sh cmake-3.9.2-Linux-x86_64.sh -- --skip-license --prefix=/usr + - sudo apt-get install -qq g++ cmake script: - mkdir build ; cd build diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ef91a5..c9c57a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +### 2.3.0 + +This release fixes various issues. + +*Fixes* + + * Fix compile error on gcc 4.9 + * Fix warnings in clang 7 + * Fix self-assignment + * Fix early destruction of server sessions + * Fix crashes in multithreaded environment (#175) + +*Additions* + + * Support calling `rpc::this_server().stop()` from a server procedure (#187) + * Make rpclib compatible with codebases that do not use exceptions + * Add `server::port()` to query the port used by the server + * Set `reuseaddress` option on the server + + +### 2.2.1 + +This release fixed a crash on Windows. + +*Fixes*: + + * Fixed client crashing when `suppress_exceptions` was on + and the server threw an exception. + ### 2.2.0 This release fixed a number of long-standing issues. diff --git a/CMakeLists.txt b/CMakeLists.txt index 58e7827d..721b0931 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 3.9.0) -project(rpc VERSION 2.2.0) +cmake_minimum_required(VERSION 3.5.1) +project(rpc VERSION 2.3.0) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") @@ -121,12 +121,12 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") list(APPEND RPCLIB_BUILD_FLAGS -Wall -pedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-prototypes - -Wno-undef -pthread) + -Wno-undef) set(UNUSED_LAMBDA_CAPTURE_WARN_SUPPORTED) check_warning_flag("unused-lambda-capture" UNUSED_LAMBDA_CAPTURE_WARN_SUPPORTED) if(${UNUSED_LAMBDA_CAPTURE_WARN_SUPPORTED}) - list(APPEND RPCLIB_BUILD_FLAGS -Wno-no-unused-lambda-capture) + list(APPEND RPCLIB_BUILD_FLAGS -Wno-unused-lambda-capture) endif() check_warning_flag("zero-as-null-pointer-constant" ZERO_AS_NULL_POINTER_CONSTANT_WARN_SUPPORTED) @@ -167,6 +167,9 @@ if(RPCLIB_COMPILE_DEFINITIONS) endif() target_link_libraries(${PROJECT_NAME} ${RPCLIB_DEP_LIBRARIES}) +if (WIN32) + target_link_libraries(${PROJECT_NAME} wsock32 ws2_32) +endif() target_include_directories( ${PROJECT_NAME} PUBLIC $ @@ -197,6 +200,7 @@ endif() # Example programs # if(RPCLIB_BUILD_EXAMPLES) + find_package(Threads) set(RPCLIB_ROOT_DIR "${PROJECT_SOURCE_DIR}") set(RPCLIB_PROJECT_NAME "${CMAKE_PROJECT_NAME}") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/localbuild") diff --git a/README.md b/README.md index 394a5224..9f008715 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ - # rpclib ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![Build Status](https://travis-ci.org/rpclib/rpclib.svg?branch=master)](https://travis-ci.org/rpclib/rpclib) [![Build status](https://ci.appveyor.com/api/projects/status/9lft2tlamcox8epq?svg=true)](https://ci.appveyor.com/project/sztomi/callme) [![Coverage Status](https://img.shields.io/codecov/c/github/rpclib/rpclib/dev.svg)](https://img.shields.io/codecov/c/github/rpclib/rpclib/dev.svg) ![Coverity](https://scan.coverity.com/projects/7259/badge.svg?flat=1) [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000)](https://gitter.im/rpclib/Lobby) -![waffle](https://badge.waffle.io/rpclib/rpclib.svg?columns=In%20Progress,Waiting%20For%20Release) +## Status + +**[rpclib is looking for maintainers](https://github.com/rpclib/rpclib/issues/273)** + +If you're looking for a similar library with support for JSON-RPC and async operations, check out **[packio](https://github.com/qchateau/packio)**. + +## Overview `rpclib` is a RPC library for C++, providing both a client and server implementation. It is built using modern C++14, and as such, requires a recent compiler. Main highlights: diff --git a/appveyor.yml b/appveyor.yml index 4d9969a6..0679387a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,29 +1,20 @@ -version: 1.0.{build} -os: Visual Studio 2015 -clone_folder: C:\projects\rpc -test: off +image: Visual Studio 2019 +configuration: Release branches: only: - master - dev -configuration: - - Release - -environment: - matrix: - - CMAKE_PLATFORM: "Visual Studio 14 2015" - - CMAKE_PLATFORM: "Visual Studio 15 2017" - -install: true - -build_script: - - cd c:\projects\rpc +before_build: - git submodule init - git submodule update --init --recursive - md build - cd build - - cmake -DRPCLIB_BUILD_TESTS=ON -G "%CMAKE_PLATFORM%" .. - - cmake --build . --config %CONFIGURATION% + - cmake .. -DRPCLIB_BUILD_TESTS=ON + +build: + project: build/rpc.sln + +test_script: - .\tests\Release\rpc_test.exe diff --git a/dependencies/include/asio/impl/error_code.ipp b/dependencies/include/asio/impl/error_code.ipp index 68674f71..c256bb53 100644 --- a/dependencies/include/asio/impl/error_code.ipp +++ b/dependencies/include/asio/impl/error_code.ipp @@ -97,20 +97,18 @@ public: #if defined(__sun) || defined(__QNX__) || defined(__SYMBIAN32__) using namespace std; return strerror(value); -#elif defined(__MACH__) && defined(__APPLE__) \ - || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) \ - || defined(_AIX) || defined(__hpux) || defined(__osf__) \ - || defined(__ANDROID__) - char buf[256] = ""; - using namespace std; - strerror_r(value, buf, sizeof(buf)); - return buf; #else char buf[256] = ""; - return strerror_r(value, buf, sizeof(buf)); + using namespace std; + return strerror_result(strerror_r(value, buf, sizeof(buf)), buf); #endif #endif // defined(ASIO_WINDOWS) } + +private: + // Helper function to adapt the result from glibc's variant of strerror_r. + static const char* strerror_result(int, const char* s) { return s; } + static const char* strerror_result(const char* s, const char*) { return s; } }; } // namespace detail diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml index 9c58a5dc..6353760f 100644 --- a/doc/mkdocs.yml +++ b/doc/mkdocs.yml @@ -1,7 +1,7 @@ docs_dir: pages site_name: rpclib -pages: +nav: - 'Home': 'index.md' - 'Guides': - 'Getting started': 'gettingstarted.md' @@ -23,6 +23,7 @@ markdown_extensions: - toc: permalink: False baselevel: 2 + toc_depth: 5 - admonition - attr_list - codehilite diff --git a/doc/pages/cookbook.md b/doc/pages/cookbook.md index 8de2879a..df0c7374 100644 --- a/doc/pages/cookbook.md +++ b/doc/pages/cookbook.md @@ -281,7 +281,7 @@ int main() { rpc::server srv(8080); // listen on TCP port 8080 std::unordered_map data; - srv.bind("store_me_maybe", [](std::string const& value) { + srv.bind("store_me_maybe", [&](std::string const& value) { auto id = rpc::this_session().id(); data[id] = value; }); diff --git a/doc/pages/reference.md b/doc/pages/reference.md index 130d1da6..ae8d0ca7 100644 --- a/doc/pages/reference.md +++ b/doc/pages/reference.md @@ -40,7 +40,7 @@ Use this class to connect to msgpack-rpc servers and call their exposed function | void | [wait_all_responses](#classrpc_1_1client_1ac37437bc05b70588c079079b957eb15f)() -

rpc::client::client

+#### rpc::client::client ```cpp rpc::client::client(std::string const &addr, uint16_t port); ``` @@ -56,7 +56,7 @@ Constructs a client. When a client is constructed, it initiates a connection asynchronically. This means that it will not block while the connection is established. However, when the first call is performed, it *might* block if the connection was not already established. -

rpc::client::~client

+#### rpc::client::~client ```cpp rpc::client::~client(); ``` @@ -67,7 +67,7 @@ Destructor. During destruction, the connection to the server is gracefully closed. This means that any outstanding reads and writes are completed first. -

rpc::client::call

+#### rpc::client::call ```cpp RPCLIB_MSGPACK::object_handle rpc::client::call(std::string const &func_name, Args... args); ``` @@ -86,7 +86,7 @@ Calls a function with the given name and arguments (if any). A RPCLIB_MSGPACK::object containing the result of the function (if any). To obtain a typed value, use the msgpack API. -

rpc::client::async_call

+#### rpc::client::async_call ```cpp std::future< RPCLIB_MSGPACK::object_handle > rpc::client::async_call(std::string const &func_name, Args... args); ``` @@ -108,7 +108,7 @@ A call is performed asynchronously in the context of the client, i.e. this is no A std::future, possibly holding a future result (which is a RPCLIB_MSGPACK::object). -

rpc::client::send

+#### rpc::client::send ```cpp void rpc::client::send(std::string const &func_name, Args... args); ``` @@ -129,7 +129,7 @@ Notifications are a special kind of calls. They can be used to notify the server !!! warn This function returns immediately (possibly before the notification is written to the socket). -

rpc::client::get_timeout

+#### rpc::client::get_timeout ```cpp nonstd::optional< int64_t > rpc::client::get_timeout() const; ``` @@ -142,7 +142,7 @@ The timeout is applied to synchronous calls. If the timeout expires without rece !!! warn The timeout has no effect on async calls. For those, the preferred timeout mechanism remains using std::future. -

rpc::client::set_timeout

+#### rpc::client::set_timeout ```cpp void rpc::client::set_timeout(int64_t value); ``` @@ -150,7 +150,7 @@ void rpc::client::set_timeout(int64_t value); Sets the timeout for synchronous calls. For more information, see -

rpc::client::clear_timeout

+#### rpc::client::clear_timeout ```cpp void rpc::client::clear_timeout(); ``` @@ -158,7 +158,7 @@ void rpc::client::clear_timeout(); Clears the timeout for synchronous calls. For more information, see -

rpc::client::get_connection_state

+#### rpc::client::get_connection_state ```cpp connection_state rpc::client::get_connection_state() const; ``` @@ -166,7 +166,7 @@ connection_state rpc::client::get_connection_state() const; Returns the current connection state. -

rpc::client::wait_all_responses

+#### rpc::client::wait_all_responses ```cpp void rpc::client::wait_all_responses(); ``` @@ -194,7 +194,7 @@ This type allows clients to handle arbitrary error objects as the msgpack-rpc sp | RPCLIB_MSGPACK::object_handle & | [get_error](#classrpc_1_1rpc__error_1a88ab8f211393ae62813042a797c08663)() -

rpc::rpc_error::get_function_name

+#### rpc::rpc_error::get_function_name ```cpp std::string rpc::rpc_error::get_function_name() const; ``` @@ -202,7 +202,7 @@ std::string rpc::rpc_error::get_function_name() const; Returns the name of the function that was called on the server while the error occurred. -

rpc::rpc_error::get_error

+#### rpc::rpc_error::get_error ```cpp RPCLIB_MSGPACK::object_handle & rpc::rpc_error::get_error(); ``` @@ -239,7 +239,7 @@ The server maintains a registry of function bindings that it uses to dispatch ca | void | [close_sessions](#classrpc_1_1server_1abf6bebbbeea52451aef2126d29240094)() -

rpc::server::server

+#### rpc::server::server ```cpp rpc::server::server(uint16_t port); ``` @@ -250,7 +250,7 @@ Constructs a server that listens on the localhost on the specified port. `port` The port number to listen on. -

rpc::server::server

+#### rpc::server::server ```cpp rpc::server::server(server &&other) noexcept; ``` @@ -261,7 +261,7 @@ Move constructor. This is implemented by calling the move assignment operator. `other` The other instance to move from. -

rpc::server::server

+#### rpc::server::server ```cpp rpc::server::server(std::string const &address, uint16_t port); ``` @@ -274,7 +274,7 @@ Constructs a server that listens on the specified address on the specified port. `port` The port number to listen on. -

rpc::server::~server

+#### rpc::server::~server ```cpp rpc::server::~server(); ``` @@ -285,7 +285,7 @@ Destructor. When the server is destroyed, all ongoin sessions are closed gracefully. -

rpc::server::operator=

+#### rpc::server::operator= ```cpp server rpc::server::operator=(server &&other); ``` @@ -299,7 +299,7 @@ Move assignment operator. The result of the assignment. -

rpc::server::run

+#### rpc::server::run ```cpp void rpc::server::run(); ``` @@ -310,7 +310,7 @@ Starts the server loop. This is a blocking call. First and foremost, running the event loop causes the server to start listening on the specified port. Also, as connections are established and calls are made by clients, the server executes the calls as part of this call. This means that the handlers are executed on the thread that calls `run`. Reads and writes are initiated by this function internally as well. -

rpc::server::async_run

+#### rpc::server::async_run ```cpp void rpc::server::async_run(std::size_t worker_threads=1); ``` @@ -324,7 +324,7 @@ Starts the server loop on one or more threads. This is a non-blocking call. This function behaves similarly to `run`, except the event loop is optionally started on different threads. Effectively this sets up a worker thread pool for the server. Handlers will be executed on one of the threads. -

rpc::server::bind

+#### rpc::server::bind ```cpp void rpc::server::bind(std::string const &name, F func); ``` @@ -343,7 +343,7 @@ Binds a functor to a name so it becomes callable via RPC. This function template accepts a wide range of callables. The arguments and return types of these callables should be serializable by msgpack. `bind` effectively generates a suitable, light-weight compile-time wrapper for the functor. -

rpc::server::suppress_exceptions

+#### rpc::server::suppress_exceptions ```cpp void rpc::server::suppress_exceptions(bool suppress); ``` @@ -353,7 +353,7 @@ Sets the exception behavior in handlers. By default, handlers throwing will cras !!! warn Setting this flag only affects subsequent connections. -

rpc::server::stop

+#### rpc::server::stop ```cpp void rpc::server::stop(); ``` @@ -363,7 +363,7 @@ Stops the server. !!! warn This should not be called from worker threads. -

rpc::server::close_sessions

+#### rpc::server::close_sessions ```cpp void rpc::server::close_sessions(); ``` @@ -394,7 +394,7 @@ Encapsulates information about the currently executing handler. This is the inte | void | [clear](#classrpc_1_1this__handler__t_1a0a53e27b4d8d5b542790b218029d26f4)() -

rpc::this_handler_t::respond_error

+#### rpc::this_handler_t::respond_error ```cpp void rpc::this_handler_t::respond_error(T &&err_obj); ``` @@ -408,7 +408,7 @@ Sets an arbitrary object to be sent back as an error response to the client. `err_obj` The error object. This can be anything that is possible to encode with messagepack (even custom structures). -

rpc::this_handler_t::respond

+#### rpc::this_handler_t::respond ```cpp void rpc::this_handler_t::respond(T &&resp_obj); ``` @@ -424,7 +424,7 @@ Sets an arbitrary object to be sent back as the response to the call. !!! warn The normal return value of the function (if any) will be ignored if a special response is set. -

rpc::this_handler_t::disable_response

+#### rpc::this_handler_t::disable_response ```cpp void rpc::this_handler_t::disable_response(); ``` @@ -434,7 +434,7 @@ Instructs the server to not send a response to the client (ignoring any errors a !!! warn It is unusual to not send a response to requests, and doing so might cause problems in the client (depending on its implementation). -

rpc::this_handler_t::enable_response

+#### rpc::this_handler_t::enable_response ```cpp void rpc::this_handler_t::enable_response(); ``` @@ -442,7 +442,7 @@ void rpc::this_handler_t::enable_response(); Enables sending a response to the call. Sending the response is by default enabled. Enabling the response multiple times have no effect. -

rpc::this_handler_t::clear

+#### rpc::this_handler_t::clear ```cpp void rpc::this_handler_t::clear(); ``` @@ -470,7 +470,7 @@ Allows controlling the server instance from the currently executing handler. | void | [cancel_stop](#classrpc_1_1this__server__t_1a127035c6f2281a5a1bfadf19f4dfe451)() -

rpc::this_server_t::stop

+#### rpc::this_server_t::stop ```cpp void rpc::this_server_t::stop(); ``` @@ -478,7 +478,7 @@ void rpc::this_server_t::stop(); Gracefully stops the server. -

rpc::this_server_t::cancel_stop

+#### rpc::this_server_t::cancel_stop ```cpp void rpc::this_server_t::cancel_stop(); ``` @@ -506,7 +506,7 @@ Encapsulates information about the server session/connection this handler is run | session_id_t | [id](#classrpc_1_1this__session__t_1aada2250ec9dd3d88781ca16eb4352047)() const -

rpc::this_session_t::post_exit

+#### rpc::this_session_t::post_exit ```cpp void rpc::this_session_t::post_exit(); ``` @@ -516,7 +516,7 @@ Gracefully exits the session (i.e. ongoing writes and reads are completed; queue !!! warn Use this function if you need to close the connection from a handler. -

rpc::this_session_t::id

+#### rpc::this_session_t::id ```cpp session_id_t rpc::this_session_t::id() const; ``` @@ -545,7 +545,7 @@ This exception is thrown by the client when either the connection or a call take | const char * | [what](#classrpc_1_1timeout_1ad782f083798c650188b5927a226c3b04)() const noexcept override -

rpc::timeout::what

+#### rpc::timeout::what ```cpp const char * rpc::timeout::what() const noexcept override; ``` diff --git a/doc/pages/versions.md b/doc/pages/versions.md index cde88106..b56c57bc 100644 --- a/doc/pages/versions.md +++ b/doc/pages/versions.md @@ -1,4 +1,4 @@ -You are reading the documentation of 2.2.0. +You are reading the documentation of 2.3.0. If, for some reason you need the documentation of older versions, you can download them from this page. * [1.0.0](/archive/rpclib_docs_1.0.0.zip) diff --git a/doc/reference.md.mako b/doc/reference.md.mako index 49c62b5f..07c4806d 100644 --- a/doc/reference.md.mako +++ b/doc/reference.md.mako @@ -41,7 +41,7 @@ ${"### Public functions"} % for f in c.functions: -

${c.name}::${f.name}

+${"#### "}${c.name}::${f.name} ```cpp ${opt(f.type)} ${c.name}::${f.name}${f.argsstr}; ``` diff --git a/doc/requirements.txt b/doc/requirements.txt index 2767e397..472c5dd8 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,4 +1,22 @@ lxml>=3.6.4 Mako>=1.0.4 -mkdocs +mkdocs>=1.0.0 pygments +xsltproc +docker + + +# Manage Docker as non-root user +https://docs.docker.com/engine/install/linux-postinstall/ + +$ sudo groupadd docker +$ sudo usermod -aG docker $USER + +# log out and in to reevaluate user groups or +$ newgrp docker + +# Verify that you can run docker commands without sudo. +$ docker run hello-world + +# If mkdocs serve is used, this is required as well +mkdocs-material diff --git a/examples/calculator/CMakeLists.txt b/examples/calculator/CMakeLists.txt index 9e08a4ef..b56e391b 100644 --- a/examples/calculator/CMakeLists.txt +++ b/examples/calculator/CMakeLists.txt @@ -2,11 +2,12 @@ cmake_minimum_required(VERSION 3.0.0) project(calculator) find_package(rpclib REQUIRED) +find_package(Threads REQUIRED) include_directories(${RPCLIB_INCLUDE_DIR}) add_executable(calculator_server calculator_server.cc) -target_link_libraries(calculator_server ${RPCLIB_LIBS}) +target_link_libraries(calculator_server ${RPCLIB_LIBS} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties( calculator_server PROPERTIES @@ -15,7 +16,7 @@ set_target_properties( target_compile_definitions(calculator_server PUBLIC ${RPCLIB_COMPILE_DEFINITIONS}) add_executable(calculator_client calculator_client.cc) -target_link_libraries(calculator_client ${RPCLIB_LIBS}) +target_link_libraries(calculator_client ${RPCLIB_LIBS} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties( calculator_client PROPERTIES diff --git a/examples/echo/CMakeLists.txt b/examples/echo/CMakeLists.txt index a84839ef..987c5055 100644 --- a/examples/echo/CMakeLists.txt +++ b/examples/echo/CMakeLists.txt @@ -2,11 +2,12 @@ cmake_minimum_required(VERSION 3.0.0) project(echo) find_package(rpclib REQUIRED) +find_package(Threads REQUIRED) include_directories(${RPCLIB_INCLUDE_DIR}) add_executable(echo_server echo_server.cc) -target_link_libraries(echo_server ${RPCLIB_LIBS}) +target_link_libraries(echo_server ${RPCLIB_LIBS} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties( echo_server PROPERTIES @@ -15,7 +16,7 @@ set_target_properties( target_compile_definitions(echo_server PUBLIC ${RPCLIB_COMPILE_DEFINITIONS}) add_executable(echo_client echo_client.cc) -target_link_libraries(echo_client ${RPCLIB_LIBS}) +target_link_libraries(echo_client ${RPCLIB_LIBS} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties( echo_client PROPERTIES diff --git a/examples/echo/echo_server.cc b/examples/echo/echo_server.cc index 6988bbdc..3c59e155 100644 --- a/examples/echo/echo_server.cc +++ b/examples/echo/echo_server.cc @@ -1,7 +1,9 @@ #include "rpc/server.h" +#include int main() { rpc::server srv(rpc::constants::DEFAULT_PORT); + std::cout << "registered on port " << srv.port() << std::endl; srv.bind("echo", [](std::string const& s) { return s; diff --git a/include/rpc/client.h b/include/rpc/client.h index 837e85c9..fcd16ebe 100644 --- a/include/rpc/client.h +++ b/include/rpc/client.h @@ -88,7 +88,7 @@ class client { //! //! \param func_name The name of the notification to call. //! \param args The arguments to pass to the function. - //! \tparam Args THe types of the arguments. + //! \tparam Args The types of the arguments. //! //! \note This function returns immediately (possibly before the //! notification is written to the socket). diff --git a/include/rpc/client.inl b/include/rpc/client.inl index 04dc825b..f980f069 100644 --- a/include/rpc/client.inl +++ b/include/rpc/client.inl @@ -47,7 +47,7 @@ client::async_call(std::string const &func_name, Args... args) { //! \param args The arguments to pass to the function. //! \note This function returns when the notification is written to the //! socket. -//! \tparam Args THe types of the arguments. +//! \tparam Args The types of the arguments. template void client::send(std::string const &func_name, Args... args) { RPCLIB_CREATE_LOG_CHANNEL(client) diff --git a/include/rpc/detail/async_writer.h b/include/rpc/detail/async_writer.h index 66488ff6..508500a7 100644 --- a/include/rpc/detail/async_writer.h +++ b/include/rpc/detail/async_writer.h @@ -23,6 +23,27 @@ class async_writer : public std::enable_shared_from_this { RPCLIB_ASIO::ip::tcp::socket socket) : socket_(std::move(socket)), write_strand_(*io), exit_(false) {} + void close() { + exit_ = true; + + auto self = shared_from_this(); + write_strand_.post([this, self]() { + LOG_INFO("Closing socket"); + std::error_code e; + socket_.shutdown( + RPCLIB_ASIO::ip::tcp::socket::shutdown_both, e); + if (e) { + LOG_WARN("std::system_error during socket shutdown. " + "Code: {}. Message: {}", e.value(), e.message()); + } + socket_.close(); + }); + } + + bool is_closed() const { + return exit_.load(); + } + void do_write() { if (exit_) { return; @@ -46,20 +67,6 @@ class async_writer : public std::enable_shared_from_this { } else { LOG_ERROR("Error while writing to socket: {}", ec); } - - if (exit_) { - LOG_INFO("Closing socket"); - try { - socket_.shutdown( - RPCLIB_ASIO::ip::tcp::socket::shutdown_both); - } - catch (std::system_error &e) { - (void)e; - LOG_WARN("std::system_error during socket shutdown. " - "Code: {}. Message: {}", e.code(), e.what()); - } - socket_.close(); - } })); } @@ -72,7 +79,9 @@ class async_writer : public std::enable_shared_from_this { do_write(); } - friend class rpc::client; + RPCLIB_ASIO::ip::tcp::socket& socket() { + return socket_; + } protected: template @@ -80,15 +89,14 @@ class async_writer : public std::enable_shared_from_this { return std::static_pointer_cast(shared_from_this()); } -protected: + RPCLIB_ASIO::strand& write_strand() { + return write_strand_; + } + +private: RPCLIB_ASIO::ip::tcp::socket socket_; RPCLIB_ASIO::strand write_strand_; std::atomic_bool exit_{false}; - bool exited_ = false; - std::mutex m_exit_; - std::condition_variable cv_exit_; - -private: std::deque write_queue_; RPCLIB_CREATE_LOG_CHANNEL(async_writer) }; diff --git a/include/rpc/detail/log.h b/include/rpc/detail/log.h index 0d5e51c6..09db735a 100644 --- a/include/rpc/detail/log.h +++ b/include/rpc/detail/log.h @@ -83,11 +83,19 @@ class logger { std::stringstream ss; timespec now_t = {}; clock_gettime(CLOCK_REALTIME, &now_t); +#if __GNUC__ >= 5 ss << std::put_time( std::localtime(reinterpret_cast(&now_t.tv_sec)), "%F %T") - << RPCLIB_FMT::format( +#else + char mltime[128]; + strftime(mltime, sizeof(mltime), "%c %Z", + std::localtime(reinterpret_cast(&now_t.tv_sec))); + ss << mltime +#endif + << RPCLIB_FMT::format( ".{:03}", round(static_cast(now_t.tv_nsec) / 1.0e6)); + return ss.str(); } #endif diff --git a/include/rpc/dispatcher.h b/include/rpc/dispatcher.h index 8044d4cb..18dca30f 100644 --- a/include/rpc/dispatcher.h +++ b/include/rpc/dispatcher.h @@ -60,6 +60,19 @@ class dispatcher { detail::tags::nonvoid_result const &, detail::tags::nonzero_arg const &); + //! \brief Unbind a functor with a given name from callable functors. + void unbind(std::string const &name) { + funcs_.erase(name); + } + + //! \brief returns a list of all names which functors are binded to + std::vector names() const { + std::vector names; + for(auto it = funcs_.begin(); it != funcs_.end(); ++it) + names.push_back(it->first); + return names; + } + //! @} //! \brief Processes a message that contains a call according to diff --git a/include/rpc/nonstd/optional.hpp b/include/rpc/nonstd/optional.hpp index d9888cf4..e6d941d5 100644 --- a/include/rpc/nonstd/optional.hpp +++ b/include/rpc/nonstd/optional.hpp @@ -564,7 +564,7 @@ class bad_optional_access : public std::logic_error public: explicit bad_optional_access() : logic_error( "bad optional access" ) {} - ~bad_optional_access(); + const char* what() const noexcept; }; /// optional diff --git a/include/rpc/rpc_error.h b/include/rpc/rpc_error.h index 558e8297..63cc1d85 100644 --- a/include/rpc/rpc_error.h +++ b/include/rpc/rpc_error.h @@ -4,6 +4,7 @@ #define RPC_ERROR_H_NEOOSTKY #include +#include #include "rpc/msgpack.hpp" @@ -17,22 +18,20 @@ namespace rpc { //! throw it, hence its constructor is private. class rpc_error : public std::runtime_error { public: + rpc_error(std::string const &what_arg, std::string const &function_name, + std::shared_ptr o); + //! \brief Returns the name of the function that was //! called on the server while the error occurred. std::string get_function_name() const; //! \brief Returns the error object that the server //! provided. - virtual RPCLIB_MSGPACK::object_handle& get_error(); - -private: - friend class client; - rpc_error(std::string const &what_arg, std::string const &function_name, - RPCLIB_MSGPACK::object_handle o); + virtual RPCLIB_MSGPACK::object_handle &get_error(); private: std::string func_name_; - RPCLIB_MSGPACK::object_handle ob_h_; + std::shared_ptr ob_h_; }; //! \brief This exception is thrown by the client when either the connection @@ -40,15 +39,23 @@ class rpc_error : public std::runtime_error { //! \note There isn't necessarily a timeout set, it is an optional value. class timeout : public std::runtime_error { public: + explicit timeout(std::string const &what_arg); + //! \brief Describes the exception. const char *what() const noexcept override; private: - friend class client; - explicit timeout(std::string const &what_arg); std::string formatted; }; +//! \brief This exception is throw by the client when the connection or call +//! causes a system error +class system_error : public std::system_error { +public: + using std::system_error::system_error; + const char* what() const noexcept; +}; + } /* rpc */ diff --git a/include/rpc/server.h b/include/rpc/server.h index f240acf1..baeb9f7c 100644 --- a/include/rpc/server.h +++ b/include/rpc/server.h @@ -93,6 +93,24 @@ class server { disp_->bind(name, func); } + //! \brief Unbinds a functor binded to a name. + //! + //! This function removes already binded function from RPC Ccallable functions + //! + //! \param name The name of the functor. + void unbind(std::string const &name) { + disp_->unbind(name); + } + + //! \brief Returns all binded names + //! + //! This function returns a list of all names which functors are binded to + //! + //! \param name The name of the functor. + std::vector names() const { + return disp_->names(); + } + //! \brief Sets the exception behavior in handlers. By default, //! handlers throwing will crash the server. If suppressing is on, //! the server will try to gather textual data and return it to @@ -104,12 +122,13 @@ class server { //! \note This should not be called from worker threads. void stop(); + //! \brief Returns port + //! \note The port + unsigned short port() const; + //! \brief Closes all sessions gracefully. void close_sessions(); - friend class detail::server_session; - -private: //! \brief Closes a specific session. void close_session(std::shared_ptr const& s); diff --git a/include/rpc/this_server.h b/include/rpc/this_server.h index 7e8826b6..d1954e4a 100644 --- a/include/rpc/this_server.h +++ b/include/rpc/this_server.h @@ -19,7 +19,8 @@ class this_server_t { //! \brief Cancels a requested stop operation. void cancel_stop(); - friend class rpc::detail::server_session; + //! Check if a stop is requested + bool stopping() const { return stopping_; } private: bool stopping_; diff --git a/include/rpc/version.h b/include/rpc/version.h index 206c7c64..47e19e92 100644 --- a/include/rpc/version.h +++ b/include/rpc/version.h @@ -6,7 +6,7 @@ namespace rpc { static constexpr unsigned VERSION_MAJOR = 2; -static constexpr unsigned VERSION_MINOR = 2; +static constexpr unsigned VERSION_MINOR = 3; static constexpr unsigned VERSION_PATCH = 0; } /* rpc */ diff --git a/lib/rpc/client.cc b/lib/rpc/client.cc index dca0f830..6ab2c56c 100644 --- a/lib/rpc/client.cc +++ b/lib/rpc/client.cc @@ -23,7 +23,8 @@ using namespace rpc::detail; namespace rpc { -static constexpr uint32_t default_buffer_size = rpc::constants::DEFAULT_BUFFER_SIZE; +static constexpr uint32_t default_buffer_size = + rpc::constants::DEFAULT_BUFFER_SIZE; struct client::impl { impl(client *parent, std::string const &addr, uint16_t port) @@ -37,14 +38,16 @@ struct client::impl { state_(client::connection_state::initial), writer_(std::make_shared( &io_, RPCLIB_ASIO::ip::tcp::socket(io_))), - timeout_(nonstd::nullopt) { + timeout_(nonstd::nullopt), + connection_ec_(nonstd::nullopt) { pac_.reserve_buffer(default_buffer_size); } void do_connect(tcp::resolver::iterator endpoint_iterator) { LOG_INFO("Initiating connection."); + connection_ec_ = nonstd::nullopt; RPCLIB_ASIO::async_connect( - writer_->socket_, endpoint_iterator, + writer_->socket(), endpoint_iterator, [this](std::error_code ec, tcp::resolver::iterator) { if (!ec) { std::unique_lock lock(mut_connection_finished_); @@ -54,7 +57,11 @@ struct client::impl { conn_finished_.notify_all(); do_read(); } else { + std::unique_lock lock(mut_connection_finished_); LOG_ERROR("Error during connection: {}", ec); + state_ = client::connection_state::disconnected; + connection_ec_ = ec; + conn_finished_.notify_all(); } }); } @@ -62,7 +69,7 @@ struct client::impl { void do_read() { LOG_TRACE("do_read"); constexpr std::size_t max_read_bytes = default_buffer_size; - writer_->socket_.async_read_some( + writer_->socket().async_read_some( RPCLIB_ASIO::buffer(pac_.buffer(), max_read_bytes), // I don't think max_read_bytes needs to be captured explicitly // (since it's constexpr), but MSVC insists. @@ -80,8 +87,7 @@ struct client::impl { if (r.get_error()) { throw rpc_error("rpc::rpc_error during call", std::get<0>(current_call), - RPCLIB_MSGPACK::clone( - r.get_error()->get())); + r.get_error()); } std::get<1>(current_call) .set_value(std::move(*r.get_result())); @@ -123,22 +129,16 @@ struct client::impl { client::connection_state get_connection_state() const { return state_; } //! \brief Waits for the write queue and writes any buffers to the network - //! connection. Should be executed throught strand_. + //! connection. Should be executed through strand_. void write(RPCLIB_MSGPACK::sbuffer item) { writer_->write(std::move(item)); } - nonstd::optional get_timeout() { - return timeout_; - } + nonstd::optional get_timeout() { return timeout_; } - void set_timeout(int64_t value) { - timeout_ = value; - } + void set_timeout(int64_t value) { timeout_ = value; } - void clear_timeout() { - timeout_ = nonstd::nullopt; - } + void clear_timeout() { timeout_ = nonstd::nullopt; } using call_t = std::pair>; @@ -158,6 +158,7 @@ struct client::impl { std::atomic state_; std::shared_ptr writer_; nonstd::optional timeout_; + nonstd::optional connection_ec_; RPCLIB_CREATE_LOG_CHANNEL(client) }; @@ -177,7 +178,11 @@ client::client(std::string const &addr, uint16_t port) void client::wait_conn() { std::unique_lock lock(pimpl->mut_connection_finished_); - if (!pimpl->is_connected_) { + while (!pimpl->is_connected_) { + if (auto ec = pimpl->connection_ec_) { + throw rpc::system_error(ec.value()); + } + if (auto timeout = pimpl->timeout_) { auto result = pimpl->conn_finished_.wait_for( lock, std::chrono::milliseconds(*timeout)); @@ -192,10 +197,7 @@ void client::wait_conn() { } } -int client::get_next_call_idx() { - ++(pimpl->call_idx_); - return pimpl->call_idx_; -} +int client::get_next_call_idx() { return ++(pimpl->call_idx_); } void client::post(std::shared_ptr buffer, int idx, std::string const &func_name, @@ -222,13 +224,9 @@ nonstd::optional client::get_timeout() const { return pimpl->get_timeout(); } -void client::set_timeout(int64_t value) { - pimpl->set_timeout(value); -} +void client::set_timeout(int64_t value) { pimpl->set_timeout(value); } -void client::clear_timeout() { - pimpl->clear_timeout(); -} +void client::clear_timeout() { pimpl->clear_timeout(); } void client::wait_all_responses() { for (auto &c : pimpl->ongoing_calls_) { @@ -236,7 +234,7 @@ void client::wait_all_responses() { } } -RPCLIB_NORETURN void client::throw_timeout(std::string const& func_name) { +RPCLIB_NORETURN void client::throw_timeout(std::string const &func_name) { throw rpc::timeout( RPCLIB_FMT::format("Timeout of {}ms while calling RPC function '{}'", *get_timeout(), func_name)); @@ -247,4 +245,4 @@ client::~client() { pimpl->io_thread_.join(); } -} +} // namespace rpc diff --git a/lib/rpc/detail/server_session.cc b/lib/rpc/detail/server_session.cc index d6f89177..dec9d3b2 100644 --- a/lib/rpc/detail/server_session.cc +++ b/lib/rpc/detail/server_session.cc @@ -33,34 +33,35 @@ void server_session::start() { do_read(); } void server_session::close() { LOG_INFO("Closing session."); - exit_ = true; - write_strand_.post([this]() { - socket_.close(); - parent_->close_session(shared_from_base()); + async_writer::close(); + + auto self(shared_from_base()); + write_strand().post([this, self]() { + parent_->close_session(self); }); } void server_session::do_read() { auto self(shared_from_base()); constexpr std::size_t max_read_bytes = default_buffer_size; - socket_.async_read_some( + socket().async_read_some( RPCLIB_ASIO::buffer(pac_.buffer(), default_buffer_size), // I don't think max_read_bytes needs to be captured explicitly // (since it's constexpr), but MSVC insists. read_strand_.wrap([this, self, max_read_bytes](std::error_code ec, std::size_t length) { - if (exit_) { return; } + if (is_closed()) { return; } if (!ec) { pac_.buffer_consumed(length); RPCLIB_MSGPACK::unpacked result; - while (pac_.next(result) && !exit_) { + while (pac_.next(result) && !is_closed()) { auto msg = result.get(); output_buf_.clear(); // any worker thread can take this call auto z = std::shared_ptr( result.zone().release()); - io_->post([this, msg, z]() { + io_->post([this, self, msg, z]() { this_handler().clear(); this_session().clear(); this_session().set_id(reinterpret_cast(this)); @@ -92,11 +93,11 @@ void server_session::do_read() { if (!resp.is_empty()) { #ifdef _MSC_VER // doesn't compile otherwise. - write_strand_.post( + write_strand().post( [=]() { write(resp.get_data()); }); #else - write_strand_.post( - [this, resp, z]() { write(resp.get_data()); }); + write_strand().post( + [this, self, resp, z]() { write(resp.get_data()); }); #endif } @@ -104,20 +105,20 @@ void server_session::do_read() { LOG_WARN("Session exit requested from a handler."); // posting through the strand so this comes after // the previous write - write_strand_.post([this]() { exit_ = true; }); + write_strand().post([this]() { close(); }); } - if (this_server().stopping_) { + if (this_server().stopping()) { LOG_WARN("Server exit requested from a handler."); // posting through the strand so this comes after // the previous write - write_strand_.post( + write_strand().post( [this]() { parent_->close_sessions(); }); } }); } - if (!exit_) { + if (!is_closed()) { // resizing strategy: if the remaining buffer size is // less than the maximum bytes requested from asio, // then request max_read_bytes. This prompts the unpacker @@ -137,9 +138,6 @@ void server_session::do_read() { LOG_ERROR("Unhandled error code: {} | '{}'", ec, ec.message()); } })); - if (exit_) { - socket_.close(); - } } } /* detail */ diff --git a/lib/rpc/nonstd/optional.cc b/lib/rpc/nonstd/optional.cc index f80c9319..ac89d298 100644 --- a/lib/rpc/nonstd/optional.cc +++ b/lib/rpc/nonstd/optional.cc @@ -3,4 +3,6 @@ // This is no-op; the reason it exists is to avoid // the weak vtables problem. For more info, see // https://stackoverflow.com/a/23749273/140367 -nonstd::bad_optional_access::~bad_optional_access() {} +const char* nonstd::bad_optional_access::what() const noexcept { + return std::logic_error::what(); +} diff --git a/lib/rpc/rpc_error.cc b/lib/rpc/rpc_error.cc index 8af70060..57df4244 100644 --- a/lib/rpc/rpc_error.cc +++ b/lib/rpc/rpc_error.cc @@ -5,14 +5,14 @@ namespace rpc { rpc_error::rpc_error(std::string const &what_arg, std::string const &function_name, - RPCLIB_MSGPACK::object_handle o) + std::shared_ptr o) : std::runtime_error(what_arg), func_name_(function_name), ob_h_(std::move(o)) {} std::string rpc_error::get_function_name() const { return func_name_; } -RPCLIB_MSGPACK::object_handle &rpc_error::get_error() { return ob_h_; } +RPCLIB_MSGPACK::object_handle &rpc_error::get_error() { return *ob_h_; } timeout::timeout(std::string const &what_arg) : std::runtime_error(what_arg) { formatted = @@ -21,4 +21,6 @@ timeout::timeout(std::string const &what_arg) : std::runtime_error(what_arg) { const char *timeout::what() const noexcept { return formatted.data(); } +const char* system_error::what() const noexcept { return std::system_error::what(); } + } /* rpc */ diff --git a/lib/rpc/server.cc b/lib/rpc/server.cc index 1012c6d9..1565b2b4 100644 --- a/lib/rpc/server.cc +++ b/lib/rpc/server.cc @@ -11,9 +11,9 @@ #include "rpc/detail/dev_utils.h" #include "rpc/detail/log.h" -#include "rpc/detail/log.h" #include "rpc/detail/server_session.h" #include "rpc/detail/thread_group.h" +#include "rpc/this_server.h" using namespace rpc::detail; using RPCLIB_ASIO::ip::tcp; @@ -25,40 +25,67 @@ struct server::impl { impl(server *parent, std::string const &address, uint16_t port) : parent_(parent), io_(), - acceptor_(io_, - tcp::endpoint(ip::address::from_string(address), port)), + acceptor_(io_), socket_(io_), - suppress_exceptions_(false) {} + suppress_exceptions_(false) { + auto ep = tcp::endpoint(ip::address::from_string(address), port); + acceptor_.open(ep.protocol()); +#ifndef _WIN32 + acceptor_.set_option(tcp::acceptor::reuse_address(true)); +#endif // !_WIN32 + acceptor_.bind(ep); + acceptor_.listen(); + } impl(server *parent, uint16_t port) : parent_(parent), io_(), - acceptor_(io_, tcp::endpoint(tcp::v4(), port)), + acceptor_(io_), socket_(io_), - suppress_exceptions_(false) {} + suppress_exceptions_(false) { + auto ep = tcp::endpoint(tcp::v4(), port); + acceptor_.open(ep.protocol()); +#ifndef _WIN32 + acceptor_.set_option(tcp::acceptor::reuse_address(true)); +#endif // !_WIN32 + acceptor_.bind(ep); + acceptor_.listen(); + } void start_accept() { acceptor_.async_accept(socket_, [this](std::error_code ec) { if (!ec) { - LOG_INFO("Accepted connection."); + auto ep = socket_.remote_endpoint(); + LOG_INFO("Accepted connection from {}:{}", ep.address(), + ep.port()); auto s = std::make_shared( parent_, &io_, std::move(socket_), parent_->disp_, suppress_exceptions_); s->start(); + std::unique_lock lock(sessions_mutex_); sessions_.push_back(s); } else { LOG_ERROR("Error while accepting connection: {}", ec); } - start_accept(); + if (!this_server().stopping()) + start_accept(); // TODO: allow graceful exit [sztomi 2016-01-13] }); } void close_sessions() { - for (auto &session : sessions_) { + std::unique_lock lock(sessions_mutex_); + auto sessions_copy = sessions_; + sessions_.clear(); + lock.unlock(); + + // release shared pointers outside of the mutex + for (auto &session : sessions_copy) { session->close(); } - sessions_.clear(); + + if (this_server().stopping()) + acceptor_.cancel(); } void stop() { @@ -66,6 +93,8 @@ struct server::impl { loop_workers_.join_all(); } + unsigned short port() const { return acceptor_.local_endpoint().port(); } + server *parent_; io_service io_; ip::tcp::acceptor acceptor_; @@ -74,23 +103,23 @@ struct server::impl { std::vector> sessions_; std::atomic_bool suppress_exceptions_; RPCLIB_CREATE_LOG_CHANNEL(server) + std::mutex sessions_mutex_; }; RPCLIB_CREATE_LOG_CHANNEL(server) server::server(uint16_t port) - : pimpl(new server::impl(this, port)), disp_(std::make_shared()) { + : pimpl(new server::impl(this, port)), + disp_(std::make_shared()) { LOG_INFO("Created server on localhost:{}", port); pimpl->start_accept(); } -server::server(server&& other) noexcept { - *this = std::move(other); -} +server::server(server &&other) noexcept { *this = std::move(other); } server::server(std::string const &address, uint16_t port) : pimpl(new server::impl(this, address, port)), - disp_(std::make_shared()) { + disp_(std::make_shared()) { LOG_INFO("Created server on address {}:{}", address, port); pimpl->start_accept(); } @@ -101,11 +130,13 @@ server::~server() { } } -server& server::operator=(server &&other) { - pimpl = std::move(other.pimpl); - other.pimpl = nullptr; - disp_ = std::move(other.disp_); - other.disp_ = nullptr; +server &server::operator=(server &&other) { + if (this != &other) { + pimpl = std::move(other.pimpl); + other.pimpl = nullptr; + disp_ = std::move(other.disp_); + other.disp_ = nullptr; + } return *this; } @@ -126,13 +157,20 @@ void server::async_run(std::size_t worker_threads) { void server::stop() { pimpl->stop(); } +unsigned short server::port() const { return pimpl->port(); } + void server::close_sessions() { pimpl->close_sessions(); } void server::close_session(std::shared_ptr const &s) { - auto it = std::find(begin(pimpl->sessions_), end(pimpl->sessions_), s); - if (it != end(pimpl->sessions_)) { - pimpl->sessions_.erase(it); - } + std::unique_lock lock(pimpl->sessions_mutex_); + auto it = std::find(begin(pimpl->sessions_), end(pimpl->sessions_), s); + std::shared_ptr session; + if (it != end(pimpl->sessions_)) { + session = *it; + pimpl->sessions_.erase(it); + } + lock.unlock(); + // session shared pointer is released outside of the mutex } -} /* rpc */ +} // namespace rpc diff --git a/tests/rpc/client_test.cc b/tests/rpc/client_test.cc index c6bee33f..a3b56f6b 100644 --- a/tests/rpc/client_test.cc +++ b/tests/rpc/client_test.cc @@ -106,9 +106,29 @@ TEST_F(client_test, timeout_clear) { EXPECT_FALSE(client.get_timeout()); } +// Only enable this test on linux +// It seems like the connection error is not detected on windows +TEST_F(client_test, bad_ip) { + rpc::client client("127.0.0.2", test_port); + client.set_timeout(1000); +#ifdef __linux__ + EXPECT_THROW(client.call("dummy_void_zeroarg"), rpc::system_error); + // We expect a connection refused, not a timeout +#else + EXPECT_ANY_THROW(client.call("dummy_void_zeroarg")); + // throw is enough for windows +#endif +} + TEST(client_test2, timeout_while_connection) { rpc::client client("localhost", rpc::constants::DEFAULT_PORT); client.set_timeout(50); +#ifdef __linux__ // this client never connects, so this tests the timout in wait_conn() - EXPECT_THROW(client.call("whatev"), rpc::timeout); + // We expect a connection refused, not a timeout + EXPECT_THROW(client.call("whatev"), rpc::system_error); +#else + EXPECT_ANY_THROW(client.call("dummy_void_zeroarg")); + // throw is enough for windows +#endif } diff --git a/tests/rpc/server_session_test.cc b/tests/rpc/server_session_test.cc index 40a08759..276d46f5 100644 --- a/tests/rpc/server_session_test.cc +++ b/tests/rpc/server_session_test.cc @@ -1,5 +1,6 @@ #include #include +#include #include "gtest/gtest.h" @@ -39,7 +40,12 @@ TEST_F(server_session_test, consume_big_param) { } TEST_F(server_session_test, connection_closed_properly) { - for (unsigned counter = 0; counter < 1000; ++counter) { +#ifdef RPCLIB_WIN32 + const unsigned max_tries = 10; +#else + const unsigned max_tries = 1000; +#endif + for (unsigned counter = 0; counter < max_tries; ++counter) { rpc::client client("localhost", rpc::constants::DEFAULT_PORT); auto response = client.call("func"); } @@ -63,7 +69,7 @@ TEST(server_session_test_bug153, bug_153_crash_on_client_timeout) { auto client = std::unique_ptr(new rpc::client("localhost", rpc::constants::DEFAULT_PORT)); client->set_timeout(5); - + try { client->call("bug_153"); } catch(rpc::timeout& ) { @@ -72,3 +78,33 @@ TEST(server_session_test_bug153, bug_153_crash_on_client_timeout) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); // no crash is enough } + + +TEST(server_session_test_bug175, bug_175_multhread_crash) { + const int nr_threads = 4; + const int nr_calls_per_thread = 100; + + rpc::server s("127.0.0.1", rpc::constants::DEFAULT_PORT); + s.bind("bug_175", [&](int idx, int i) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return 0; + }); + s.async_run(nr_threads); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + auto spam_call = [&](int idx, int nr_calls) { + for (int i=0; i(new rpc::client("localhost", rpc::constants::DEFAULT_PORT)); + client->call("bug_175", idx, i); + } + }; + + std::vector> futures; + for (int i=0; i + #include "rpc/client.h" #include "rpc/server.h" #include "rpc/this_server.h" @@ -24,7 +26,7 @@ class this_server_test : public testing::Test { rpc::client c2; }; -TEST_F(this_server_test, stop) { +TEST_F(this_server_test, stop_async) { s.bind("stop_server", []() { rpc::this_server().stop(); }); s.async_run(); c1.call("stop_server"); @@ -35,3 +37,21 @@ TEST_F(this_server_test, stop) { EXPECT_EQ(c2.get_connection_state(), client::connection_state::disconnected); } + +TEST_F(this_server_test, stop_sync) { + s.bind("stop_server", []() { rpc::this_server().stop(); }); + + auto handle = std::async(std::launch::async, [this]() { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + c1.call("stop_server"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + }); + + s.run(); + handle.get(); + + EXPECT_EQ(c1.get_connection_state(), + client::connection_state::disconnected); + EXPECT_EQ(c2.get_connection_state(), + client::connection_state::disconnected); +}