diff --git a/.travis.yml b/.travis.yml index a40c5c3..4a9b9c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,17 +22,7 @@ matrix: env: - CONAN_APPLE_CLANG_VERSIONS=11.0 - # Test supported dependency versions - - <<: *linux - env: - - CONAN_GCC_VERSIONS=7 - - CONAN_DOCKER_IMAGE=conanio/gcc7 - - CONAN_OPTIONS=boost=1.70.0 - - <<: *linux - env: - - CONAN_GCC_VERSIONS=7 - - CONAN_DOCKER_IMAGE=conanio/gcc7 - - CONAN_OPTIONS=boost=1.71.0 + # Test supported boost versions - <<: *linux env: - CONAN_GCC_VERSIONS=7 diff --git a/README.md b/README.md index df5d23a..2b3a66a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ ## Header-only | msgpack-RPC | Boost.Asio -This library requires C++17 and is designed as an extension to Boost.Asio. It will let you built asynchronous servers or client for msgpack-RPC. +This library requires C++17 and is designed as an extension to Boost.Asio. It will let you build asynchronous servers or client for msgpack-RPC. -The library is still under development and is therefore subject heavy API changes. - -The project is hosted on [GitHub](https://github.com/qchateau/packio/) and packaged with [Conan](https://bintray.com/beta/#/qchateau/qchateau/packio:_?tab=overview). Documentation is available on [GitHub Pages](https://qchateau.github.io/packio/). +The project is hosted on [GitHub](https://github.com/qchateau/packio/) and available on [Conan Center](https://conan.io/center/). Documentation is available on [GitHub Pages](https://qchateau.github.io/packio/). ## Primer @@ -21,7 +19,11 @@ server->dispatcher()->add("add", [](int a, int b) { return a + b; }); // Declare an asynchronous callback server->dispatcher()->add_async( "multiply", [](packio::completion_handler complete, int a, int b) { - complete(a * b); + // Call the completion handler later + boost::asio::post( + io, [a, b, complete = std::move(complete)]() mutable { + complete(a * b); + }); }); // Accept connections forever @@ -34,20 +36,26 @@ client->async_call("add", std::make_tuple(42, 24), [&](boost::system::error_code, msgpack::object r) { std::cout << "The result is: " << r.as() << std::endl; }); -// Use as<> wrapper to hide result type conversion + +// Use boost::asio::use_future +std::future add_future = client->async_call( + "add", std::tuple{12, 23}, boost::asio::use_future); +std::cout << "The result is: " << add_future.get()->as() << std::endl; + +// Use auto result type conversion client->async_call( "multiply", std::make_tuple(42, 24), - packio::as([&](boost::system::error_code, std::optional r) { - multiply_result.set_value(*r); - })); + [&](boost::system::error_code, std::optional r) { + std::cout << "The result is: " << *r << std::endl; + }); ``` ## Requirements - C++17 -- Boost.Asio >= 1.70.0 -- msgpack >= 3.0.1 +- Boost.Asio >= 1.72.0 +- msgpack >= 3.2.1 ## Tested compilers @@ -65,10 +73,8 @@ client->async_call( ## Conan -Add my remote and install packio: ```bash -conan remote add qchateau https://api.bintray.com/conan/qchateau/qchateau -conan install -r qchateau packio/0.8.0 +conan install packio/1.1.0 ``` ## Bonus @@ -106,18 +112,15 @@ int main(int argc, char** argv) client->async_call( "fibonacci", std::tuple{n - 1}, - [=, &client, complete = std::move(complete)]( - boost::system::error_code, - msgpack::object_handle result1) mutable { - int r1 = result1->as(); + [n, &client, complete = std::move(complete)]( + boost::system::error_code, std::optional r1) mutable { client->async_call( "fibonacci", std::tuple{n - 2}, - [=, complete = std::move(complete)]( + [r1, complete = std::move(complete)]( boost::system::error_code, - msgpack::object_handle result2) mutable { - int r2 = result2->as(); - complete(r1 + r2); + std::optional r2) mutable { + complete(*r1 + *r2); }); }); }); @@ -129,9 +132,9 @@ int main(int argc, char** argv) client->async_call( "fibonacci", - std::make_tuple(n), - [&](boost::system::error_code, msgpack::object_handle r) { - result = r->as(); + std::tuple{n}, + [&](boost::system::error_code, std::optional r) { + result = *r; io.stop(); }); @@ -141,5 +144,4 @@ int main(int argc, char** argv) return 0; } - ``` diff --git a/conanfile.py b/conanfile.py index efa27b6..d627f04 100644 --- a/conanfile.py +++ b/conanfile.py @@ -3,7 +3,7 @@ class PackioConan(ConanFile): name = "packio" - version = "1.0.2" + version = "1.1.0" license = "MPL-2.0" author = "Quentin Chateau " url = "https://github.com/qchateau/packio" @@ -13,7 +13,7 @@ class PackioConan(ConanFile): no_copy_source = True requires = [ "msgpack/3.2.1", - "boost/[>=1.70]", + "boost/[>=1.72]", ] def package(self): diff --git a/docs/annotated.html b/docs/annotated.html index 6ef3613..b7d9436 100644 --- a/docs/annotated.html +++ b/docs/annotated.html @@ -68,13 +68,11 @@
[detail level 123]
- - - - - - - + + + + + diff --git a/docs/classes.html b/docs/classes.html index 4a8c175..8ac67ad 100644 --- a/docs/classes.html +++ b/docs/classes.html @@ -67,17 +67,17 @@
a | c | d | n | s
 NpackioThe packio namespace
 Ntraits
 CAsCallHandlerAsCallHandler
 CAsVoidCallHandlerAsVoidCallHandler
 CAsyncProcedureAsyncProcedure trait
 CCallHandlerCallHandler trait
 CNotifyHandlerNotifyHandler trait
 CServeHandlerServeHandler trait
 CSyncProcedureSyncProcedure trait
 CAsyncProcedureAsyncProcedure trait
 CCallHandlerCallHandler trait
 CNotifyHandlerNotifyHandler trait
 CServeHandlerServeHandler trait
 CSyncProcedureSyncProcedure trait
 CclientThe client class
 Ccompletion_handlerThe completion_handler class
 CdispatcherThe dispatcher class, used to store and dispatch procedures
+ + + - - - - - - + + + +
  a  
-
  c  
-
  d  
+
client (packio)   
  n  
+
server (packio)   
completion_handler (packio)   server_session (packio)   
AsyncProcedure (packio::traits)   
  d  
+
NotifyHandler (packio::traits)   SyncProcedure (packio::traits)   
  c  
  s  
-
SyncProcedure (packio::traits)   
AsCallHandler (packio::traits)   CallHandler (packio::traits)   dispatcher (packio)   ServeHandler (packio::traits)   
AsVoidCallHandler (packio::traits)   client (packio)   
  n  
-
server (packio)   
AsyncProcedure (packio::traits)   completion_handler (packio)   server_session (packio)   
NotifyHandler (packio::traits)   
dispatcher (packio)   
CallHandler (packio::traits)   ServeHandler (packio::traits)   
a | c | d | n | s
diff --git a/docs/classpackio_1_1client-members.html b/docs/classpackio_1_1client-members.html index fb2fa83..50a7801 100644 --- a/docs/classpackio_1_1client-members.html +++ b/docs/classpackio_1_1client-members.html @@ -71,10 +71,10 @@

This is the complete list of members for packio::client< Protocol, Map >, including all inherited members.

- - - - + + + + diff --git a/docs/classpackio_1_1client.html b/docs/classpackio_1_1client.html index 6117d31..bb066d0 100644 --- a/docs/classpackio_1_1client.html +++ b/docs/classpackio_1_1client.html @@ -125,22 +125,22 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
async_call(std::string_view name, std::tuple< Args... > args, CallHandler &&handler)packio::client< Protocol, Map >inline
async_call(std::string_view name, CallHandler &&handler)packio::client< Protocol, Map >inline
async_notify(std::string_view name, std::tuple< Args... > args, NotifyHandler &&handler)packio::client< Protocol, Map >inline
async_notify(std::string_view name, NotifyHandler &&handler)packio::client< Protocol, Map >inline
async_call(std::string_view name, const std::tuple< Args... > &args, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)packio::client< Protocol, Map >inline
async_call(std::string_view name, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)packio::client< Protocol, Map >inline
async_notify(std::string_view name, const std::tuple< Args... > &args, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())packio::client< Protocol, Map >inline
async_notify(std::string_view name, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())packio::client< Protocol, Map >inline
cancel(id_type id)packio::client< Protocol, Map >inline
cancel()packio::client< Protocol, Map >inline
client(socket_type socket)packio::client< Protocol, Map >inlineexplicit
void cancel ()
 Cancel all pending calls. More...
 
template<typename Buffer = msgpack::sbuffer, typename NotifyHandler , typename... Args>
void async_notify (std::string_view name, std::tuple< Args... > args, NotifyHandler &&handler)
 Send a notify request to the server with argument. More...
 
template<typename Buffer = msgpack::sbuffer, typename NotifyHandler >
void async_notify (std::string_view name, NotifyHandler &&handler)
 Send a notify request to the server with no argument. More...
 
template<typename Buffer = msgpack::sbuffer, typename CallHandler , typename... Args>
id_type async_call (std::string_view name, std::tuple< Args... > args, CallHandler &&handler)
 Call a remote procedure. More...
 
template<typename Buffer = msgpack::sbuffer, typename CallHandler >
id_type async_call (std::string_view name, CallHandler &&handler)
 Call a remote procedure. More...
 
template<typename Buffer = msgpack::sbuffer, typename NotifyHandler = typename boost::asio::default_completion_token<executor_type>::type, typename... Args>
auto async_notify (std::string_view name, const std::tuple< Args... > &args, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())
 Send a notify request to the server with argument. More...
 
template<typename Buffer = msgpack::sbuffer, typename NotifyHandler = typename boost::asio::default_completion_token<executor_type>::type>
auto async_notify (std::string_view name, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())
 Send a notify request to the server with no argument. More...
 
template<typename Buffer = msgpack::sbuffer, typename CallHandler = typename boost::asio::default_completion_token<executor_type>::type, typename... Args>
auto async_call (std::string_view name, const std::tuple< Args... > &args, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)
 Call a remote procedure. More...
 
template<typename Buffer = msgpack::sbuffer, typename CallHandler = typename boost::asio::default_completion_token<executor_type>::type>
auto async_call (std::string_view name, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)
 Call a remote procedure. More...
 
@@ -199,21 +199,21 @@

Member Function Documentation

- -

◆ async_call() [1/2]

+ +

◆ async_call() [1/2]

template<typename Protocol , template< class... > class Map = std::map>
-template<typename Buffer = msgpack::sbuffer, typename CallHandler , typename... Args>
+template<typename Buffer = msgpack::sbuffer, typename CallHandler = typename boost::asio::default_completion_token<executor_type>::type, typename... Args>

Static Public Attributes

- + @@ -221,14 +221,20 @@

- + - + + + + + + + @@ -259,29 +265,29 @@

- + +
id_type packio::client< Protocol, Map >::async_call auto packio::client< Protocol, Map >::async_call ( std::string_view  name, std::tuple< Args... > const std::tuple< Args... > &  args,
CallHandler && handler handler = typename boost::asio::default_completion_token<executor_type>::type(),
std::optional< std::reference_wrapper< id_type >> call_id = std::nullopt 
nameRemote procedure name to call
argsTuple of arguments to pass to the remote procedure
handlerHandler called with the return value Must satisfy the traits::CallHandler trait
handlerHandler called with the return value
call_idOutput parameter that will receive the call ID Must satisfy the traits::CallHandler trait
-
Returns
The call ID
-
-

◆ async_call() [2/2]

+ +

◆ async_call() [2/2]

template<typename Protocol , template< class... > class Map = std::map>
-template<typename Buffer = msgpack::sbuffer, typename CallHandler >
+template<typename Buffer = msgpack::sbuffer, typename CallHandler = typename boost::asio::default_completion_token<executor_type>::type>
- + @@ -290,7 +296,13 @@

- + + + + + + + @@ -310,21 +322,21 @@

-

◆ async_notify() [1/2]

+ +

◆ async_notify() [1/2]

template<typename Protocol , template< class... > class Map = std::map>
-template<typename Buffer = msgpack::sbuffer, typename NotifyHandler , typename... Args>
+template<typename Buffer = msgpack::sbuffer, typename NotifyHandler = typename boost::asio::default_completion_token<executor_type>::type, typename... Args>
id_type packio::client< Protocol, Map >::async_call auto packio::client< Protocol, Map >::async_call ( std::string_view  name, CallHandler && handler handler = typename boost::asio::default_completion_token<executor_type>::type(),
std::optional< std::reference_wrapper< id_type >> call_id = std::nullopt 
- - - - - - diff --git a/docs/traits_8h_source.html b/docs/traits_8h_source.html index 2d4e2d0..9a2e107 100644 --- a/docs/traits_8h_source.html +++ b/docs/traits_8h_source.html @@ -68,15 +68,13 @@
traits.h
-Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_TRAITS_H
6 #define PACKIO_TRAITS_H
7 
10 
11 #include <type_traits>
12 #include <utility>
13 #include <boost/asio.hpp>
14 #include <msgpack.hpp>
15 
16 #include "internal/utils.h"
17 
18 #if defined(PACKIO_TRAITS_CHECK_DISABLE) && PACKIO_TRAITS_CHECK_DISABLE
19 #define PACKIO_STATIC_ASSERT_TRAIT(Trait) (void)0
20 #define PACKIO_STATIC_ASSERT_TTRAIT(Trait, ...) (void)0
21 #else
22 #define PACKIO_STATIC_ASSERT_TRAIT(Trait) \
23  static_assert( \
24  ::packio::traits::Trait<Trait>::value, "Trait " #Trait " not satisfied")
25 #define PACKIO_STATIC_ASSERT_TTRAIT(Trait, ...) \
26  static_assert( \
27  ::packio::traits::Trait<Trait, __VA_ARGS__>::value, \
28  "Trait " #Trait " not satisfied")
29 #endif
30 
31 namespace packio {
32 
33 class completion_handler;
34 
35 namespace traits {
36 
37 namespace details {
38 
39 template <typename, typename = void>
40 struct AsyncProcedureImpl : std::false_type {
41 };
42 
43 template <typename T>
44 struct AsyncProcedureImpl<T, std::enable_if_t<internal::func_traits_v<T>>> {
45  using TArgs = typename internal::func_traits<T>::args_type;
46  static constexpr bool value = [] {
47  if constexpr (std::tuple_size_v<TArgs> == 0) {
48  return false;
49  }
50  else {
51  return std::is_same_v<std::decay_t<std::tuple_element_t<0, TArgs>>, completion_handler>;
52  }
53  }();
54 };
55 
56 template <typename, typename = void>
57 struct SyncProcedureImpl : std::false_type {
58 };
59 
60 template <typename T>
61 struct SyncProcedureImpl<T, std::enable_if_t<internal::func_traits_v<T>>>
62  : std::true_type {
63 };
64 
65 } // details
66 
67 template <bool condition>
68 struct Trait : std::integral_constant<bool, condition> {
69 };
70 
75 template <typename T>
76 struct NotifyHandler : Trait<std::is_invocable_v<T, boost::system::error_code>> {
77 };
78 
83 template <typename T>
85  : Trait<std::is_invocable_v<T, boost::system::error_code, msgpack::object_handle>> {
86 };
87 
92 template <typename T, typename Result>
94  : Trait<std::is_invocable_v<T, boost::system::error_code, std::optional<Result>>> {
95 };
96 
101 template <typename T>
103  : Trait<std::is_invocable_v<T, boost::system::error_code>> {
104 };
105 
111 template <typename T, typename Session>
113  : Trait<std::is_invocable_v<T, boost::system::error_code, std::shared_ptr<Session>>> {
114 };
115 
122 template <typename T>
123 struct AsyncProcedure : Trait<details::AsyncProcedureImpl<T>::value> {
124 };
125 
132 template <typename T>
133 struct SyncProcedure : Trait<details::SyncProcedureImpl<T>::value> {
134 };
135 
136 } // traits
137 } // packio
138 
139 #endif // PACKIO_TRAITS_H
NotifyHandler trait.
Definition: traits.h:76
-
CallHandler trait.
Definition: traits.h:84
+Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_TRAITS_H
6 #define PACKIO_TRAITS_H
7 
10 
11 #include <type_traits>
12 #include <utility>
13 #include <boost/asio.hpp>
14 #include <msgpack.hpp>
15 
16 #include "internal/utils.h"
17 
18 #if defined(PACKIO_TRAITS_CHECK_DISABLE) && PACKIO_TRAITS_CHECK_DISABLE
19 #define PACKIO_STATIC_ASSERT_TRAIT(Trait) (void)0
20 #define PACKIO_STATIC_ASSERT_TTRAIT(Trait, ...) (void)0
21 #else
22 #define PACKIO_STATIC_ASSERT_TRAIT(Trait) \
23  static_assert( \
24  ::packio::traits::Trait<Trait>::value, "Trait " #Trait " not satisfied")
25 #define PACKIO_STATIC_ASSERT_TTRAIT(Trait, ...) \
26  static_assert( \
27  ::packio::traits::Trait<Trait, __VA_ARGS__>::value, \
28  "Trait " #Trait " not satisfied")
29 #endif
30 
31 namespace packio {
32 
33 class completion_handler;
34 
35 namespace traits {
36 
37 namespace details {
38 
39 template <typename, typename = void>
40 struct AsyncProcedureImpl : std::false_type {
41 };
42 
43 template <typename T>
44 struct AsyncProcedureImpl<T, std::enable_if_t<internal::func_traits_v<T>>> {
45  using TArgs = typename internal::func_traits<T>::args_type;
46  static constexpr bool value = [] {
47  if constexpr (std::tuple_size_v<TArgs> == 0) {
48  return false;
49  }
50  else {
51  return std::is_same_v<std::decay_t<std::tuple_element_t<0, TArgs>>, completion_handler>;
52  }
53  }();
54 };
55 
56 template <typename, typename = void>
57 struct SyncProcedureImpl : std::false_type {
58 };
59 
60 template <typename T>
61 struct SyncProcedureImpl<T, std::enable_if_t<internal::func_traits_v<T>>>
62  : std::true_type {
63 };
64 
65 } // details
66 
67 template <bool condition>
68 struct Trait : std::integral_constant<bool, condition> {
69 };
70 
75 template <typename T>
76 struct NotifyHandler : Trait<std::is_invocable_v<T, boost::system::error_code>> {
77 };
78 
85 template <typename T>
86 struct CallHandler : Trait<internal::is_valid_call_handler_v<T>> {
87 };
88 
94 template <typename T, typename Session>
96  : Trait<std::is_invocable_v<T, boost::system::error_code, std::shared_ptr<Session>>> {
97 };
98 
105 template <typename T>
106 struct AsyncProcedure : Trait<details::AsyncProcedureImpl<T>::value> {
107 };
108 
115 template <typename T>
116 struct SyncProcedure : Trait<details::SyncProcedureImpl<T>::value> {
117 };
118 
119 } // traits
120 } // packio
121 
122 #endif // PACKIO_TRAITS_H
NotifyHandler trait.
Definition: traits.h:76
+
CallHandler trait.
Definition: traits.h:86
STL namespace.
-
ServeHandler trait.
Definition: traits.h:112
-
SyncProcedure trait.
Definition: traits.h:133
-
AsyncProcedure trait.
Definition: traits.h:123
-
The packio namespace.
Definition: as.h:16
-
AsVoidCallHandler.
Definition: traits.h:102
-
AsCallHandler.
Definition: traits.h:93
+
ServeHandler trait.
Definition: traits.h:95
+
SyncProcedure trait.
Definition: traits.h:116
+
AsyncProcedure trait.
Definition: traits.h:106
+
The packio namespace.
Definition: client.h:30
diff --git a/docs/unique__function_8h_source.html b/docs/unique__function_8h_source.html index ead004a..e539413 100644 --- a/docs/unique__function_8h_source.html +++ b/docs/unique__function_8h_source.html @@ -69,7 +69,7 @@
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_UNIQUE_FUNCTION_H
6 #define PACKIO_UNIQUE_FUNCTION_H
7 
8 #include <functional>
9 #include <type_traits>
10 #include <utility>
11 
12 namespace packio {
13 namespace internal {
14 
15 template <typename T>
16 class unique_function : public std::function<T> {
17  template <typename Fn, typename En = void>
18  struct wrapper {
19  };
20 
21  // specialization for CopyConstructible Fn
22  template <typename Fn>
23  struct wrapper<Fn, std::enable_if_t<std::is_copy_constructible<Fn>::value>> {
24  Fn fn;
25 
26  template <typename... Args>
27  auto operator()(Args&&... args)
28  {
29  return fn(std::forward<Args>(args)...);
30  }
31  };
32 
33  // specialization for MoveConstructible-only Fn
34  template <typename Fn>
35  struct wrapper<
36  Fn,
37  std::enable_if_t<
38  !std::is_copy_constructible<Fn>::value
39  && std::is_move_constructible<Fn>::value>> {
40  Fn fn;
41 
42  wrapper(Fn&& fn) : fn(std::forward<Fn>(fn)) {}
43 
44  wrapper(wrapper&&) = default;
45  wrapper& operator=(wrapper&&) = default;
46 
47  // these two functions are instantiated by std::function and are never called
48  wrapper(const wrapper& rhs) : fn(const_cast<Fn&&>(rhs.fn))
49  {
50  // hack to initialize fn for non-DefaultContructible types
51  std::abort();
52  }
53  wrapper& operator=(wrapper&) { std::abort(); }
54 
55  template <typename... Args>
56  auto operator()(Args&&... args)
57  {
58  return fn(std::forward<Args>(args)...);
59  }
60  };
61 
62  using base = std::function<T>;
63 
64 public:
65  unique_function() noexcept = default;
66  unique_function(std::nullptr_t) noexcept : base(nullptr) {}
67 
68  template <typename Fn>
69  unique_function(Fn&& f) : base(wrapper<Fn>{std::forward<Fn>(f)})
70  {
71  }
72 
73  unique_function(unique_function&&) = default;
74  unique_function& operator=(unique_function&&) = default;
75 
76  unique_function& operator=(std::nullptr_t)
77  {
78  base::operator=(nullptr);
79  return *this;
80  }
81 
82  template <typename Fn>
83  unique_function& operator=(Fn&& f)
84  {
85  base::operator=(wrapper<Fn>{std::forward<Fn>(f)});
86  return *this;
87  }
88 
89  using base::operator();
90 };
91 
92 } // internal
93 } // packio
94 
95 #endif // PACKIO_UNIQUE_FUNCTION_H
STL namespace.
-
The packio namespace.
Definition: as.h:16
+
The packio namespace.
Definition: client.h:30
diff --git a/docs/utils_8h_source.html b/docs/utils_8h_source.html index 694f859..e521b01 100644 --- a/docs/utils_8h_source.html +++ b/docs/utils_8h_source.html @@ -68,8 +68,9 @@
utils.h
-
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_UTILS_H
6 #define PACKIO_UTILS_H
7 
8 #include <sstream>
9 #include <string_view>
10 #include <type_traits>
11 #include <vector>
12 
13 #include <boost/asio.hpp>
14 #include <msgpack.hpp>
15 
16 #include "log.h"
17 
18 namespace packio {
19 namespace internal {
20 
21 template <typename, typename = void>
22 struct func_traits : std::false_type {
23 };
24 
25 template <typename T>
26 struct func_traits<T, std::void_t<decltype(&std::decay_t<T>::operator())>>
27  : func_traits<decltype(&std::decay_t<T>::operator())> {
28 };
29 
30 template <typename C, typename R, typename... Args>
31 struct func_traits<R (C::*)(Args...)> : func_traits<R (*)(Args...)> {
32 };
33 
34 template <typename C, typename R, typename... Args>
35 struct func_traits<R (C::*)(Args...) const> : func_traits<R (*)(Args...)> {
36 };
37 
38 template <typename R, typename... Args>
39 struct func_traits<R (*)(Args...)> : std::true_type {
40  using result_type = R;
41  using args_type = std::tuple<Args...>;
42 };
43 
44 template <typename T>
45 constexpr bool func_traits_v = func_traits<T>::value;
46 
47 template <typename T>
48 struct shift_tuple;
49 
50 template <typename A, typename... Bs>
51 struct shift_tuple<std::tuple<A, Bs...>> {
52  using type = std::tuple<Bs...>;
53 };
54 
55 template <typename T>
56 using shift_tuple_t = typename shift_tuple<T>::type;
57 
58 template <typename T>
59 struct decay_tuple;
60 
61 template <typename... Args>
62 struct decay_tuple<std::tuple<Args...>> {
63  using type = std::tuple<std::decay_t<Args>...>;
64 };
65 
66 template <typename T>
67 using decay_tuple_t = typename decay_tuple<T>::type;
68 
69 inline boost::asio::const_buffer buffer(const msgpack::sbuffer& buf)
70 {
71  return boost::asio::const_buffer(buf.data(), buf.size());
72 }
73 
74 inline std::vector<boost::asio::const_buffer> buffer(const msgpack::vrefbuffer& buf)
75 {
76  std::vector<boost::asio::const_buffer> vec;
77  vec.reserve(buf.vector_size());
78  const struct iovec* iov = buf.vector();
79  for (std::size_t i = 0; i < buf.vector_size(); ++i) {
80  vec.push_back(boost::asio::const_buffer(iov->iov_base, iov->iov_len));
81  ++iov;
82  }
83  return vec;
84 }
85 
86 template <typename T>
87 msgpack::object_handle make_msgpack_object(T&& value)
88 {
89  msgpack::object_handle oh({}, std::make_unique<msgpack::zone>());
90  oh.set(msgpack::object(std::forward<T>(value), *oh.zone()));
91  return oh;
92 }
93 
94 template <typename T>
95 void set_no_delay(T&)
96 {
97 }
98 
99 template <>
100 inline void set_no_delay(boost::asio::ip::tcp::socket& socket)
101 {
102  socket.set_option(boost::asio::ip::tcp::no_delay{true});
103 }
104 
105 } // internal
106 } // packio
107 
108 #endif // PACKIO_UTILS_H
STL namespace.
-
The packio namespace.
Definition: as.h:16
+
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_UTILS_H
6 #define PACKIO_UTILS_H
7 
8 #include <sstream>
9 #include <string_view>
10 #include <type_traits>
11 #include <vector>
12 
13 #include <boost/asio.hpp>
14 #include <msgpack.hpp>
15 
16 #include "log.h"
17 
18 namespace packio {
19 namespace internal {
20 
21 template <typename, typename = void>
22 struct func_traits : std::false_type {
23 };
24 
25 template <typename T>
26 struct func_traits<T, std::void_t<decltype(&std::decay_t<T>::operator())>>
27  : func_traits<decltype(&std::decay_t<T>::operator())> {
28 };
29 
30 template <typename C, typename R, typename... Args>
31 struct func_traits<R (C::*)(Args...)> : func_traits<R (*)(Args...)> {
32 };
33 
34 template <typename C, typename R, typename... Args>
35 struct func_traits<R (C::*)(Args...) const> : func_traits<R (*)(Args...)> {
36 };
37 
38 template <typename R, typename... Args>
39 struct func_traits<R (*)(Args...)> : std::true_type {
40  using result_type = R;
41  using args_type = std::tuple<Args...>;
42  static constexpr auto args_count = std::tuple_size_v<args_type>;
43 };
44 
45 template <typename T>
46 constexpr bool func_traits_v = func_traits<T>::value;
47 
48 template <typename T>
49 struct shift_tuple;
50 
51 template <typename A, typename... Bs>
52 struct shift_tuple<std::tuple<A, Bs...>> {
53  using type = std::tuple<Bs...>;
54 };
55 
56 template <typename T>
57 using shift_tuple_t = typename shift_tuple<T>::type;
58 
59 template <typename T>
60 struct decay_tuple;
61 
62 template <typename... Args>
63 struct decay_tuple<std::tuple<Args...>> {
64  using type = std::tuple<std::decay_t<Args>...>;
65 };
66 
67 template <typename T>
68 using decay_tuple_t = typename decay_tuple<T>::type;
69 
70 inline boost::asio::const_buffer buffer(const msgpack::sbuffer& buf)
71 {
72  return boost::asio::const_buffer(buf.data(), buf.size());
73 }
74 
75 inline std::vector<boost::asio::const_buffer> buffer(const msgpack::vrefbuffer& buf)
76 {
77  std::vector<boost::asio::const_buffer> vec;
78  vec.reserve(buf.vector_size());
79  const struct iovec* iov = buf.vector();
80  for (std::size_t i = 0; i < buf.vector_size(); ++i) {
81  vec.push_back(boost::asio::const_buffer(iov->iov_base, iov->iov_len));
82  ++iov;
83  }
84  return vec;
85 }
86 
87 template <typename T>
88 msgpack::object_handle make_msgpack_object(T&& value)
89 {
90  msgpack::object_handle oh({}, std::make_unique<msgpack::zone>());
91  oh.set(msgpack::object(std::forward<T>(value), *oh.zone()));
92  return oh;
93 }
94 
95 template <typename T>
96 void set_no_delay(T&)
97 {
98 }
99 
100 template <>
101 inline void set_no_delay(boost::asio::ip::tcp::socket& socket)
102 {
103  socket.set_option(boost::asio::ip::tcp::no_delay{true});
104 }
105 
106 struct incompatible_handler_t {
107 };
108 
109 template <typename Handler>
110 auto wrap_call_handler(Handler&& handler)
111 {
112  if constexpr (std::is_invocable_v<Handler, boost::system::error_code, msgpack::object_handle>) {
113  // handler takes the object handler as argument
114  return std::forward<Handler>(handler);
115  }
116  else if constexpr (std::is_invocable_v<Handler, boost::system::error_code, std::nullopt_t>) {
117  // handler takes an optional to a type, try to convert
118  using args = typename func_traits<Handler>::args_type;
119  using opt_result_type = std::tuple_element_t<1, args>;
120  using result_type = typename opt_result_type::value_type;
121  return [handler = std::forward<Handler>(handler)](
122  boost::system::error_code ec,
123  msgpack::object_handle result) mutable {
124  if (ec) {
125  handler(ec, std::nullopt);
126  }
127  else {
128  try {
129  handler(ec, result->as<result_type>());
130  }
131  catch (msgpack::type_error&) {
132  ec = make_error_code(error::bad_result_type);
133  handler(ec, std::nullopt);
134  }
135  }
136  };
137  }
138  else if constexpr (std::is_invocable_v<Handler, boost::system::error_code>) {
139  // handler takes no argument, just call with the error code
140  return [handler = std::forward<Handler>(handler)](
141  boost::system::error_code ec,
142  msgpack::object_handle) mutable { handler(ec); };
143  }
144  else {
145  // handler cannot be used as a call handler
146  // return a special type to identify this using traits
147  return incompatible_handler_t{};
148  }
149 }
150 
151 template <typename CallHandler>
152 constexpr bool is_valid_call_handler_v = !std::is_same_v<
153  incompatible_handler_t,
154  std::decay_t<decltype(wrap_call_handler(std::declval<CallHandler>()))>>;
155 
156 } // internal
157 } // packio
158 
159 #endif // PACKIO_UTILS_H
STL namespace.
+
The result type is not as expected.
+
The packio namespace.
Definition: client.h:30
diff --git a/include/packio/as.h b/include/packio/as.h deleted file mode 100644 index 832fb92..0000000 --- a/include/packio/as.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef PACKIO_AS_H -#define PACKIO_AS_H - -//! @file -//! Function @ref packio::as "as" - -#include -#include -#include -#include - -#include "error_code.h" -#include "internal/utils.h" -#include "traits.h" - -namespace packio { - -//! Function used to wrap a typed call handler -//! -//! This function is used to provide a call handler that expects a specific -//! return type. If the procedure call succeeds but the returned type is not -//! what is expected, the handler will be called with @ref -//! error::bad_result_type. The optional given as second argument will have a -//! value only if the error code is @ref error::success. -//! -//! @tparam Result The expected return type for the procedure -//! @param handler Call handler to wrap. Must satisfy @ref traits::AsCallHandler -template -auto as( - AsCallHandler&& handler, - std::enable_if_t, void*> = nullptr) -{ - PACKIO_STATIC_ASSERT_TTRAIT(AsCallHandler, Result); - return [handler = std::forward(handler)]( - boost::system::error_code ec, - msgpack::object_handle result) mutable { - if (ec) { - handler(ec, std::nullopt); - } - else { - try { - handler(ec, std::optional{result->as()}); - } - catch (msgpack::type_error&) { - ec = make_error_code(error::bad_result_type); - handler(ec, std::nullopt); - } - } - }; -} - -//! Function used to wrap a call handler that expects a void result -//! -//! @overload -//! The only point of this wrapper is to verify that the remote procedure -//! returned void. If the remote procedure returns any other type, this wrapper -//! will set the error code to @ref error::bad_result_type. -template -auto as( - AsVoidCallHandler&& handler, - std::enable_if_t, void*> = nullptr) -{ - PACKIO_STATIC_ASSERT_TRAIT(AsVoidCallHandler); - return [handler = std::forward(handler)]( - boost::system::error_code ec, - msgpack::object_handle result) mutable { - if (!ec && result->type != msgpack::type::NIL) { - ec = make_error_code(error::bad_result_type); - } - handler(ec); - }; -} - -} // packio - -#endif // PACKIO_AS_H diff --git a/include/packio/client.h b/include/packio/client.h index 0e04b65..7f886c2 100644 --- a/include/packio/client.h +++ b/include/packio/client.h @@ -10,10 +10,12 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -76,11 +78,11 @@ class client : public std::enable_shared_from_this> { //! @param id The call ID of the call to cancel void cancel(id_type id) { - boost::asio::dispatch(call_strand_, [this, self = shared_from_this(), id] { + boost::asio::dispatch(call_strand_, [self = shared_from_this(), id] { auto ec = make_error_code(error::cancelled); - async_call_handler( + self->async_call_handler( id, internal::make_msgpack_object(ec.message()), ec); - maybe_stop_reading(); + self->maybe_stop_reading(); }); } @@ -89,16 +91,16 @@ class client : public std::enable_shared_from_this> { //! The associated handlers will be called with @ref error::cancelled void cancel() { - boost::asio::dispatch(call_strand_, [this, self = shared_from_this()] { - assert(call_strand_.running_in_this_thread()); + boost::asio::dispatch(call_strand_, [self = shared_from_this()] { + assert(self->call_strand_.running_in_this_thread()); auto ec = make_error_code(error::cancelled); - while (!pending_.empty()) { - async_call_handler( - pending_.begin()->first, + while (!self->pending_.empty()) { + self->async_call_handler( + self->pending_.begin()->first, internal::make_msgpack_object(ec.message()), ec); } - maybe_stop_reading(); + self->maybe_stop_reading(); }); } @@ -113,44 +115,34 @@ class client : public std::enable_shared_from_this> { //! @param args Tuple of arguments to pass to the remote procedure //! @param handler Handler called after the notify request is sent. //! Must satisfy the @ref traits::NotifyHandler trait - template - void async_notify( + template < + typename Buffer = msgpack::sbuffer, + typename NotifyHandler = + typename boost::asio::default_completion_token::type, + typename... Args> + auto async_notify( std::string_view name, - std::tuple args, - NotifyHandler&& handler) + const std::tuple& args, + NotifyHandler&& handler = + typename boost::asio::default_completion_token::type()) { - PACKIO_STATIC_ASSERT_TRAIT(NotifyHandler); - PACKIO_DEBUG("async_notify: {}", name); - - auto packer_buf = std::make_unique(); - msgpack::pack( - *packer_buf, - std::forward_as_tuple( - static_cast(msgpack_rpc_type::notification), name, args)); - - async_send( - std::move(packer_buf), - [handler = std::forward(handler)]( - boost::system::error_code ec, std::size_t length) mutable { - if (ec) { - PACKIO_WARN("write error: {}", ec.message()); - } - else { - PACKIO_TRACE("write: {}", length); - (void)length; - } - - handler(ec); - }); + return boost::asio::async_initiate( + initiate_async_notify(this), handler, name, args); } //! Send a notify request to the server with no argument //! @overload - template - void async_notify(std::string_view name, NotifyHandler&& handler) + template < + typename Buffer = msgpack::sbuffer, + typename NotifyHandler = + typename boost::asio::default_completion_token::type> + auto async_notify( + std::string_view name, + NotifyHandler&& handler = + typename boost::asio::default_completion_token::type()) { return async_notify( - name, std::tuple<>{}, std::forward(handler)); + name, std::tuple{}, std::forward(handler)); } //! Call a remote procedure @@ -161,71 +153,45 @@ class client : public std::enable_shared_from_this> { //! @param name Remote procedure name to call //! @param args Tuple of arguments to pass to the remote procedure //! @param handler Handler called with the return value + //! @param call_id Output parameter that will receive the call ID //! Must satisfy the @ref traits::CallHandler trait - //! @return The call ID - template - id_type async_call( + template < + typename Buffer = msgpack::sbuffer, + typename CallHandler = + typename boost::asio::default_completion_token::type, + typename... Args> + auto async_call( std::string_view name, - std::tuple args, - CallHandler&& handler) + const std::tuple& args, + CallHandler&& handler = + typename boost::asio::default_completion_token::type(), + std::optional> call_id = std::nullopt) { - PACKIO_STATIC_ASSERT_TRAIT(CallHandler); - PACKIO_DEBUG("async_call: {}", name); - - auto id = id_.fetch_add(1, std::memory_order_acq_rel); - auto packer_buf = std::make_unique(); - msgpack::pack( - *packer_buf, - std::forward_as_tuple( - static_cast(msgpack_rpc_type::request), id, name, args)); - - boost::asio::dispatch( - call_strand_, - [this, - self = shared_from_this(), - id, - handler = std::forward(handler), - packer_buf = std::move(packer_buf)]() mutable { - // we must emplace the id and handler before sending data - // otherwise we might drop a fast response - assert(call_strand_.running_in_this_thread()); - pending_.try_emplace(id, std::forward(handler)); - - // if we are not reading, start the read operation - if (!reading_) { - PACKIO_DEBUG("start reading"); - async_read(std::make_unique()); - } - - // send the request buffer - async_send( - std::move(packer_buf), - [this, self = std::move(self), id]( - boost::system::error_code ec, std::size_t length) mutable { - if (ec) { - PACKIO_WARN("write error: {}", ec.message()); - async_call_handler( - id, - internal::make_msgpack_object(ec.message()), - ec); - } - else { - PACKIO_TRACE("write: {}", length); - (void)length; - } - }); - }); - - return id; + id_type tmp_id; + return boost::asio::async_initiate< + CallHandler, + void(boost::system::error_code, msgpack::object_handle)>( + initiate_async_call(this), + handler, + name, + args, + call_id.value_or(tmp_id)); } //! Call a remote procedure //! @overload - template - id_type async_call(std::string_view name, CallHandler&& handler) + template < + typename Buffer = msgpack::sbuffer, + typename CallHandler = + typename boost::asio::default_completion_token::type> + auto async_call( + std::string_view name, + CallHandler&& handler = + typename boost::asio::default_completion_token::type(), + std::optional> call_id = std::nullopt) { return async_call( - name, std::tuple<>{}, std::forward(handler)); + name, std::tuple{}, std::forward(handler), call_id); } private: @@ -260,12 +226,11 @@ class client : public std::enable_shared_from_this> { boost::asio::async_write( socket_, buf, - [this, - self = std::move(self), + [self = std::move(self), buffer_ptr = std::move(buffer_ptr), handler = std::forward(handler)]( boost::system::error_code ec, size_t length) mutable { - wstrand_.next(); + self->wstrand_.next(); handler(ec, length); }); }); @@ -284,32 +249,32 @@ class client : public std::enable_shared_from_this> { buffer, boost::asio::bind_executor( call_strand_, - [this, self = shared_from_this(), unpacker = std::move(unpacker)]( + [self = shared_from_this(), unpacker = std::move(unpacker)]( boost::system::error_code ec, size_t length) mutable { PACKIO_TRACE("read: {}", length); unpacker->buffer_consumed(length); msgpack::object_handle response; while (unpacker->next(response)) { - process_response(std::move(response), ec); + self->process_response(std::move(response), ec); } // stop if there is an error or there is no more pending calls - assert(call_strand_.running_in_this_thread()); + assert(self->call_strand_.running_in_this_thread()); if (ec && ec != boost::asio::error::operation_aborted) { PACKIO_WARN("read error: {}", ec.message()); - reading_ = false; + self->reading_ = false; return; } - if (pending_.empty()) { + if (self->pending_.empty()) { PACKIO_TRACE("done reading, no more pending calls"); - reading_ = false; + self->reading_ = false; return; } - async_read(std::move(unpacker)); + self->async_read(std::move(unpacker)); })); } @@ -386,6 +351,128 @@ class client : public std::enable_shared_from_this> { return true; } + template + class initiate_async_notify { + public: + using executor_type = typename client::executor_type; + + explicit initiate_async_notify(client* self) : self_(self) {} + + executor_type get_executor() const noexcept + { + return self_->get_executor(); + } + + template + void operator()( + NotifyHandler&& handler, + std::string_view name, + const std::tuple& args) const + { + PACKIO_STATIC_ASSERT_TRAIT(NotifyHandler); + PACKIO_DEBUG("async_notify: {}", name); + + auto packer_buf = std::make_unique(); + msgpack::pack( + *packer_buf, + std::forward_as_tuple( + static_cast(msgpack_rpc_type::notification), name, args)); + + self_->async_send( + std::move(packer_buf), + [handler = std::forward(handler)]( + boost::system::error_code ec, std::size_t length) mutable { + if (ec) { + PACKIO_WARN("write error: {}", ec.message()); + } + else { + PACKIO_TRACE("write: {}", length); + (void)length; + } + + handler(ec); + }); + } + + private: + client* self_; + }; + + template + class initiate_async_call { + public: + using executor_type = typename client::executor_type; + + explicit initiate_async_call(client* self) : self_(self) {} + + executor_type get_executor() const noexcept + { + return self_->get_executor(); + } + + template + void operator()( + CallHandler&& handler, + std::string_view name, + const std::tuple& args, + id_type& call_id) const + { + PACKIO_STATIC_ASSERT_TRAIT(CallHandler); + PACKIO_DEBUG("async_call: {}", name); + + call_id = self_->id_.fetch_add(1, std::memory_order_acq_rel); + auto packer_buf = std::make_unique(); + msgpack::pack( + *packer_buf, + std::forward_as_tuple( + static_cast(msgpack_rpc_type::request), + call_id, + name, + args)); + + boost::asio::dispatch( + self_->call_strand_, + [self = self_->shared_from_this(), + call_id, + handler = internal::wrap_call_handler( + std::forward(handler)), + packer_buf = std::move(packer_buf)]() mutable { + // we must emplace the id and handler before sending data + // otherwise we might drop a fast response + assert(self->call_strand_.running_in_this_thread()); + self->pending_.try_emplace(call_id, std::move(handler)); + + // if we are not reading, start the read operation + if (!self->reading_) { + PACKIO_DEBUG("start reading"); + self->async_read(std::make_unique()); + } + + // send the request buffer + self->async_send( + std::move(packer_buf), + [self = std::move(self), call_id]( + boost::system::error_code ec, + std::size_t length) mutable { + if (ec) { + PACKIO_WARN("write error: {}", ec.message()); + self->async_call_handler( + call_id, + internal::make_msgpack_object(ec.message()), + ec); + } + else { + PACKIO_TRACE("write: {}", length); + (void)length; + } + }); + }); + } + + private: + client* self_; + }; + socket_type socket_; std::size_t buffer_reserve_size_{kDefaultBufferReserveSize}; std::atomic id_{0}; diff --git a/include/packio/handler.h b/include/packio/handler.h index 021d960..06aa459 100644 --- a/include/packio/handler.h +++ b/include/packio/handler.h @@ -46,9 +46,22 @@ class completion_handler { completion_handler& operator=(const completion_handler&) = delete; //! Move constructor - completion_handler(completion_handler&&) = default; + completion_handler(completion_handler&& other) + : handler_{std::move(other.handler_)} + { + other.handler_ = nullptr; + } + //! Move assignment operator - completion_handler& operator=(completion_handler&&) = default; + completion_handler& operator=(completion_handler&& other) + { + if (handler_) { + set_error("Call finished with no result"); + } + handler_ = std::move(other.handler_); + other.handler_ = nullptr; + return *this; + } //! Notify successful completion of the procedure and set the return value //! @param return_value The value that the procedure will return to the client @@ -94,6 +107,7 @@ class completion_handler { handler_(make_error_code(err), std::move(result)); handler_ = nullptr; } + function_type handler_; }; } // packio diff --git a/include/packio/internal/utils.h b/include/packio/internal/utils.h index 1e3efc0..21eadbd 100644 --- a/include/packio/internal/utils.h +++ b/include/packio/internal/utils.h @@ -39,6 +39,7 @@ template struct func_traits : std::true_type { using result_type = R; using args_type = std::tuple; + static constexpr auto args_count = std::tuple_size_v; }; template @@ -102,6 +103,56 @@ inline void set_no_delay(boost::asio::ip::tcp::socket& socket) socket.set_option(boost::asio::ip::tcp::no_delay{true}); } +struct incompatible_handler_t { +}; + +template +auto wrap_call_handler(Handler&& handler) +{ + if constexpr (std::is_invocable_v) { + // handler takes the object handler as argument + return std::forward(handler); + } + else if constexpr (std::is_invocable_v) { + // handler takes an optional to a type, try to convert + using args = typename func_traits::args_type; + using opt_result_type = std::tuple_element_t<1, args>; + using result_type = typename opt_result_type::value_type; + return [handler = std::forward(handler)]( + boost::system::error_code ec, + msgpack::object_handle result) mutable { + if (ec) { + handler(ec, std::nullopt); + } + else { + try { + handler(ec, result->as()); + } + catch (msgpack::type_error&) { + ec = make_error_code(error::bad_result_type); + handler(ec, std::nullopt); + } + } + }; + } + else if constexpr (std::is_invocable_v) { + // handler takes no argument, just call with the error code + return [handler = std::forward(handler)]( + boost::system::error_code ec, + msgpack::object_handle) mutable { handler(ec); }; + } + else { + // handler cannot be used as a call handler + // return a special type to identify this using traits + return incompatible_handler_t{}; + } +} + +template +constexpr bool is_valid_call_handler_v = !std::is_same_v< + incompatible_handler_t, + std::decay_t()))>>; + } // internal } // packio diff --git a/include/packio/packio.h b/include/packio/packio.h index 2580727..4c7c1da 100644 --- a/include/packio/packio.h +++ b/include/packio/packio.h @@ -8,7 +8,6 @@ //! @namespace packio //! The packio namespace -#include "as.h" #include "client.h" #include "dispatcher.h" #include "error_code.h" diff --git a/include/packio/server.h b/include/packio/server.h index b0bdd1d..35c7f0d 100644 --- a/include/packio/server.h +++ b/include/packio/server.h @@ -79,8 +79,7 @@ class server : public std::enable_shared_from_this> PACKIO_STATIC_ASSERT_TTRAIT(ServeHandler, session_type); PACKIO_TRACE("async_serve"); acceptor_.async_accept( - [this, - self = shared_from_this(), + [self = shared_from_this(), handler = std::forward(handler)]( boost::system::error_code ec, socket_type sock) mutable { std::shared_ptr session; @@ -90,7 +89,7 @@ class server : public std::enable_shared_from_this> else { internal::set_no_delay(sock); session = std::make_shared( - std::move(sock), dispatcher_ptr_); + std::move(sock), self->dispatcher_ptr_); } handler(ec, std::move(session)); }); @@ -99,13 +98,13 @@ class server : public std::enable_shared_from_this> //! Accept connections and automatically start the associated sessions forever void async_serve_forever() { - async_serve([this, self = shared_from_this()](auto ec, auto session) { + async_serve([self = shared_from_this()](auto ec, auto session) { if (ec) { return; } session->start(); - async_serve_forever(); + self->async_serve_forever(); }); } diff --git a/include/packio/server_session.h b/include/packio/server_session.h index 679edab..c1d22b9 100644 --- a/include/packio/server_session.h +++ b/include/packio/server_session.h @@ -83,11 +83,11 @@ class server_session unpacker->buffer(), unpacker->buffer_capacity()); socket_.async_read_some( buffer, - [this, self = shared_from_this(), unpacker = std::move(unpacker)]( + [self = shared_from_this(), unpacker = std::move(unpacker)]( boost::system::error_code ec, size_t length) mutable { if (ec) { PACKIO_WARN("read error: {}", ec.message()); - close_connection(); + self->close_connection(); return; } @@ -99,10 +99,10 @@ class server_session // to schedule the next read immediately // this will allow parallel call handling // in multi-threaded environments - async_dispatch(std::move(call)); + self->async_dispatch(std::move(call)); } - async_read(std::move(unpacker)); + self->async_read(std::move(unpacker)); }); } @@ -110,8 +110,8 @@ class server_session { boost::asio::post( socket_.get_executor(), - [this, self = shared_from_this(), call = std::move(call)] { - dispatch(call.get()); + [self = shared_from_this(), call = std::move(call)] { + self->dispatch(call.get()); }); } @@ -124,11 +124,11 @@ class server_session } auto completion_handler = - [this, type = call->type, id = call->id, self = shared_from_this()]( + [type = call->type, id = call->id, self = shared_from_this()]( boost::system::error_code ec, msgpack::object_handle result) { if (type == msgpack_rpc_type::request) { PACKIO_TRACE("result: {}", ec.message()); - async_send_result(id, ec, std::move(result)); + self->async_send_result(id, ec, std::move(result)); } }; @@ -239,15 +239,13 @@ class server_session boost::asio::async_write( socket_, buf, - [this, - self = std::move(self), - message_ptr = std::move(message_ptr)]( + [self = std::move(self), message_ptr = std::move(message_ptr)]( boost::system::error_code ec, size_t length) { - wstrand_.next(); + self->wstrand_.next(); if (ec) { PACKIO_WARN("write error: {}", ec.message()); - close_connection(); + self->close_connection(); return; } diff --git a/include/packio/traits.h b/include/packio/traits.h index 67c7622..1955262 100644 --- a/include/packio/traits.h +++ b/include/packio/traits.h @@ -78,29 +78,12 @@ struct NotifyHandler : Trait> //! CallHandler trait //! -//! Handler used by @ref client::async_call +//! Handler used by @ref client::async_call, must meet one of: //! - Must be callable with boost::system::error_code, msgpack::object_handle -template -struct CallHandler - : Trait> { -}; - -//! AsCallHandler -//! -//! Handler wrapped by @ref as -//! - Must be callable with boost::system::error_code, std::optional -template -struct AsCallHandler - : Trait>> { -}; - -//! AsVoidCallHandler -//! -//! Handler wrapped by @ref as //! - Must be callable with boost::system::error_code +//! - Must be callable with boost::system::error_code, std::optional template -struct AsVoidCallHandler - : Trait> { +struct CallHandler : Trait> { }; //! ServeHandler trait diff --git a/samples/basic.cpp b/samples/basic.cpp index 26dc981..b356cbf 100644 --- a/samples/basic.cpp +++ b/samples/basic.cpp @@ -17,8 +17,12 @@ int main(int, char**) server->dispatcher()->add("add", [](int a, int b) { return a + b; }); // Declare an asynchronous callback server->dispatcher()->add_async( - "multiply", [](packio::completion_handler complete, int a, int b) { - complete(a * b); + "multiply", [&io](packio::completion_handler complete, int a, int b) { + // Call the completion handler later + boost::asio::post( + io, [a, b, complete = std::move(complete)]() mutable { + complete(a * b); + }); }); // Connect the client @@ -32,24 +36,28 @@ int main(int, char**) std::promise add_result, multiply_result; client->async_call( "add", - std::make_tuple(42, 24), + std::tuple{42, 24}, [&](boost::system::error_code, msgpack::object_handle r) { add_result.set_value(r->as()); }); + std::cout << "42 + 24 = " << add_result.get_future().get() << std::endl; + + // Use boost::asio::use_future + std::future add_future = client->async_call( + "add", std::tuple{12, 23}, boost::asio::use_future); + std::cout << "12 + 23 = " << add_future.get()->as() << std::endl; - // Use as<> wrapper to hide result type conversion + // Use auto result type conversion client->async_call( "multiply", - std::make_tuple(42, 24), - packio::as([&](boost::system::error_code, std::optional r) { + std::tuple{42, 24}, + [&](boost::system::error_code, std::optional r) { multiply_result.set_value(*r); - })); - - std::cout << "42 + 24 = " << add_result.get_future().get() << std::endl; + }); std::cout << "42 * 24 = " << multiply_result.get_future().get() << std::endl; io.stop(); thread.join(); return 0; -} \ No newline at end of file +} diff --git a/samples/fibonacci.cpp b/samples/fibonacci.cpp index 08a306e..94695ab 100644 --- a/samples/fibonacci.cpp +++ b/samples/fibonacci.cpp @@ -28,18 +28,15 @@ int main(int argc, char** argv) client->async_call( "fibonacci", std::tuple{n - 1}, - [=, &client, complete = std::move(complete)]( - boost::system::error_code, - msgpack::object_handle result1) mutable { - int r1 = result1->as(); + [n, &client, complete = std::move(complete)]( + boost::system::error_code, std::optional r1) mutable { client->async_call( "fibonacci", std::tuple{n - 2}, - [=, complete = std::move(complete)]( + [r1, complete = std::move(complete)]( boost::system::error_code, - msgpack::object_handle result2) mutable { - int r2 = result2->as(); - complete(r1 + r2); + std::optional r2) mutable { + complete(*r1 + *r2); }); }); }); @@ -51,9 +48,9 @@ int main(int argc, char** argv) client->async_call( "fibonacci", - std::make_tuple(n), - [&](boost::system::error_code, msgpack::object_handle r) { - result = r->as(); + std::tuple{n}, + [&](boost::system::error_code, std::optional r) { + result = *r; io.stop(); }); diff --git a/test_package/basic.cpp b/test_package/basic.cpp index 9154189..e663c14 100644 --- a/test_package/basic.cpp +++ b/test_package/basic.cpp @@ -12,6 +12,7 @@ using namespace std::chrono; using namespace boost::asio; using namespace packio; +using boost::asio::use_future; using std::this_thread::sleep_for; template @@ -51,25 +52,6 @@ typedef ::testing::Types< std::pair, server>> Implementations; -class packio_exception : public std::system_error { -public: - packio_exception(std::error_code ec, msgpack::object result) - : system_error(ec) - { - if (result.type == msgpack::type::STR) { - result_message_ = result.as(); - } - else { - result_message_ = "not a string"; - } - } - - std::string result_message() const { return result_message_; } - -private: - std::string result_message_; -}; - template class Test : public ::testing::Test { protected: @@ -106,64 +88,6 @@ class Test : public ::testing::Test { client_->socket().connect(ep); } - template - auto future_notify(std::string_view name, Args&&... args) - { - std::promise p; - auto f = p.get_future(); - client_->async_notify( - name, - std::forward_as_tuple(args...), - [p = std::move(p)](auto ec) mutable { - if (ec) { - p.set_exception( - std::make_exception_ptr(std::system_error(ec))); - } - else { - p.set_value(); - } - }); - return f; - } - - template - auto future_call(std::string_view name, Args&&... args) - { - id_type id; - return future_call(id, name, std::forward(args)...); - } - - template - auto future_call(id_type& id, std::string_view name, Args&&... args) - { - std::promise p; - auto f = p.get_future(); - id = client_->async_call( - name, - std::forward_as_tuple(args...), - [p = std::move(p)](auto ec, auto result) mutable { - if (ec) { - p.set_exception(std::make_exception_ptr( - packio_exception(ec, result.get()))); - } - else { - if constexpr (std::is_void_v) { - if (result->type != msgpack::type::NIL) { - p.set_exception(std::make_exception_ptr( - std::runtime_error("bad result type"))); - } - else { - p.set_value(); - } - } - else { - p.set_value(result->template as()); - } - } - }); - return f; - } - boost::asio::io_context io_; std::shared_ptr server_; std::shared_ptr client_; @@ -206,7 +130,7 @@ TYPED_TEST(Test, test_typical_usage) { call_latch.reset(1); - auto f = this->future_notify("echo", 42); + auto f = this->client_->async_notify("echo", std::tuple{42}, use_future); ASSERT_EQ(std::future_status::ready, f.wait_for(std::chrono::seconds{1})); ASSERT_NO_THROW(f.get()); ASSERT_TRUE(call_latch.wait_for(std::chrono::seconds{1})); @@ -217,14 +141,14 @@ TYPED_TEST(Test, test_typical_usage) call_latch.reset(1); call_arg_received = 0; - auto f = this->template future_call("echo", 42); + auto f = this->client_->async_call("echo", std::tuple{42}, use_future); ASSERT_EQ(std::future_status::ready, f.wait_for(std::chrono::seconds{1})); - ASSERT_EQ(42, f.get()); + ASSERT_EQ(42, f.get()->template as()); ASSERT_EQ(42, call_arg_received.load()); } } -TYPED_TEST(Test, test_as) +TYPED_TEST(Test, test_implicit_result_conversion) { this->server_->async_serve_forever(); this->connect(); @@ -233,7 +157,7 @@ TYPED_TEST(Test, test_as) this->server_->dispatcher()->add("add", [](int a, int b) { return a + b; }); this->server_->dispatcher()->add("void", [] {}); - // test valid call + // one argument, valid { std::promise done; auto future_done = done.get_future(); @@ -241,76 +165,52 @@ TYPED_TEST(Test, test_as) this->client_->async_call( "add", std::tuple{12, 21}, - as( - [&done](boost::system::error_code ec, std::optional result) { - ASSERT_FALSE(ec); - ASSERT_EQ(33, *result); - done.set_value(); - })); - - ASSERT_EQ( - std::future_status::ready, - future_done.wait_for(std::chrono::seconds{1})); - } - - // test as valid call - { - std::promise done; - auto future_done = done.get_future(); - - this->client_->async_call( - "void", as([&done](boost::system::error_code ec) { + [&done](boost::system::error_code ec, std::optional result) { ASSERT_FALSE(ec); + ASSERT_EQ(33, *result); done.set_value(); - })); + }); ASSERT_EQ( std::future_status::ready, future_done.wait_for(std::chrono::seconds{1})); } - // test invalid call + // no argument, valid { std::promise done; auto future_done = done.get_future(); - this->client_->async_call( - "add", - std::tuple{"hello", "you"}, - as( - [&done](boost::system::error_code ec, std::optional result) { - ASSERT_EQ(::packio::error::call_error, ec); - ASSERT_FALSE(result); - done.set_value(); - })); + this->client_->async_call("void", [&done](boost::system::error_code ec) { + ASSERT_FALSE(ec); + done.set_value(); + }); ASSERT_EQ( std::future_status::ready, future_done.wait_for(std::chrono::seconds{1})); } - // test invalid return type + // one argument, bad call { std::promise done; auto future_done = done.get_future(); this->client_->async_call( "add", - std::tuple{12, 21}, - as([&done]( - boost::system::error_code ec, - std::optional result) { - ASSERT_EQ(::packio::error::bad_result_type, ec); + std::tuple{"hello", "you"}, + [&done](boost::system::error_code ec, std::optional result) { + ASSERT_EQ(::packio::error::call_error, ec); ASSERT_FALSE(result); done.set_value(); - })); + }); ASSERT_EQ( std::future_status::ready, future_done.wait_for(std::chrono::seconds{1})); } - // test as invalid return type + // one argument, invalid type { std::promise done; auto future_done = done.get_future(); @@ -318,10 +218,12 @@ TYPED_TEST(Test, test_as) this->client_->async_call( "add", std::tuple{12, 21}, - as([&done](boost::system::error_code ec) { + [&done]( + boost::system::error_code ec, std::optional result) { ASSERT_EQ(::packio::error::bad_result_type, ec); + ASSERT_FALSE(result); done.set_value(); - })); + }); ASSERT_EQ( std::future_status::ready, @@ -356,24 +258,23 @@ TYPED_TEST(Test, test_timeout) const auto assert_blocks = [](auto& future) { ASSERT_EQ( std::future_status::timeout, - future.wait_for(std::chrono::milliseconds{1})); + future.wait_for(std::chrono::milliseconds{100})); }; const auto assert_cancelled = [](auto& future) { ASSERT_EQ( - std::future_status::ready, - future.wait_for(std::chrono::milliseconds{1})); + std::future_status::ready, future.wait_for(std::chrono::seconds{1})); try { future.get(); ASSERT_FALSE(true); // never reached } - catch (std::system_error& err) { + catch (boost::system::system_error& err) { ASSERT_EQ(make_error_code(packio::error::cancelled), err.code()); } }; { - auto f1 = this->template future_call("block"); - auto f2 = this->template future_call("block"); + auto f1 = this->client_->async_call("block", use_future); + auto f2 = this->client_->async_call("block", use_future); assert_blocks(f1); assert_blocks(f2); this->client_->cancel(); @@ -388,8 +289,8 @@ TYPED_TEST(Test, test_timeout) { id_type id1, id2; - auto f1 = this->template future_call(id1, "block"); - auto f2 = this->template future_call(id2, "block"); + auto f1 = this->client_->async_call("block", use_future, id1); + auto f2 = this->client_->async_call("block", use_future, id2); assert_blocks(f1); assert_blocks(f2); this->client_->cancel(id2); @@ -408,9 +309,9 @@ TYPED_TEST(Test, test_timeout) } { - auto f = this->template future_call("block"); + auto f = this->client_->async_call("block", use_future); assert_blocks(f); - this->template future_call("unblock").get(); + this->client_->async_call("unblock", use_future).get(); f.get(); } @@ -467,44 +368,95 @@ TYPED_TEST(Test, test_functions) return std::tuple{i, s}; }); - ASSERT_NO_THROW(this->template future_call("async_void_void").get()); - ASSERT_EQ(42, this->template future_call("async_int_void").get()); - ASSERT_NO_THROW(this->template future_call("async_void_int", 42).get()); - ASSERT_EQ(42, this->template future_call("async_int_int", 42).get()); - ASSERT_EQ(42, this->template future_call("async_int_intref", 42).get()); + ASSERT_NO_THROW( + this->client_->async_call("async_void_void", use_future).get()); + ASSERT_EQ( + 42, + this->client_->async_call("async_int_void", use_future) + .get() + ->template as()); + ASSERT_NO_THROW( + this->client_->async_call("async_void_int", std::tuple{42}, use_future) + .get()); ASSERT_EQ( 42, - this->template future_call("async_int_intref_int", 42, 24).get()); + this->client_->async_call("async_int_int", std ::tuple{42}, use_future) + .get() + ->template as()); + ASSERT_EQ( + 42, + this->client_->async_call("async_int_intref", std::tuple{42}, use_future) + .get() + ->template as()); + ASSERT_EQ( + 42, + this->client_ + ->async_call("async_int_intref_int", std::tuple{42, 24}, use_future) + .get() + ->template as()); ASSERT_EQ( "foobar", - this->template future_call("async_str_str", "foobar").get()); + this->client_ + ->async_call("async_str_str", std::make_tuple("foobar"), use_future) + .get() + ->template as()); ASSERT_EQ( "foobar", - this->template future_call("async_str_strref", "foobar").get()); + this->client_ + ->async_call("async_str_strref", std::make_tuple("foobar"), use_future) + .get() + ->template as()); ASSERT_EQ( tuple_int_str(42, "foobar"), - this->template future_call( - "async_tuple_int_str", 42, "foobar") - .get()); + this->client_ + ->async_call( + "async_tuple_int_str", std::make_tuple(42, "foobar"), use_future) + .get() + ->template as()); - ASSERT_NO_THROW(this->template future_call("sync_void_void").get()); - ASSERT_EQ(42, this->template future_call("sync_int_void").get()); - ASSERT_NO_THROW(this->template future_call("sync_void_int", 42).get()); - ASSERT_EQ(42, this->template future_call("sync_int_int", 42).get()); - ASSERT_EQ(42, this->template future_call("sync_int_intref", 42).get()); + ASSERT_NO_THROW(this->client_->async_call("sync_void_void", use_future).get()); + ASSERT_EQ( + 42, + this->client_->async_call("sync_int_void", use_future) + .get() + ->template as()); + ASSERT_NO_THROW( + this->client_->async_call("sync_void_int", std::tuple{42}, use_future).get()); + ASSERT_EQ( + 42, + this->client_->async_call("sync_int_int", std::tuple{42}, use_future) + .get() + ->template as()); + ASSERT_EQ( + 42, + this->client_->async_call("sync_int_intref", std::tuple{42}, use_future) + .get() + ->template as()); ASSERT_EQ( - 42, this->template future_call("sync_int_intref_int", 42, 24).get()); + 42, + this->client_ + ->async_call("sync_int_intref_int", std::tuple{42, 24}, use_future) + .get() + ->template as()); ASSERT_EQ( "foobar", - this->template future_call("sync_str_str", "foobar").get()); + this->client_ + ->async_call("sync_str_str", std::make_tuple("foobar"), use_future) + .get() + ->template as()); ASSERT_EQ( "foobar", - this->template future_call("sync_str_strref", "foobar").get()); + this->client_ + ->async_call("sync_str_strref", std::make_tuple("foobar"), use_future) + .get() + ->template as()); ASSERT_EQ( tuple_int_str(42, "foobar"), - this->template future_call( - "sync_tuple_int_str", 42, "foobar") - .get()); + this->client_ + ->async_call( + "sync_tuple_int_str", std::make_tuple(42, "foobar"), use_future) + .get() + ->template as()); } TYPED_TEST(Test, test_dispatcher) @@ -524,8 +476,8 @@ TYPED_TEST(Test, test_dispatcher) ASSERT_FALSE(this->server_->dispatcher()->add("f001", []() {})); ASSERT_FALSE(this->server_->dispatcher()->add("f002", []() {})); - this->template future_call("f001").get(); - this->template future_call("f002").get(); + this->client_->async_call("f001", use_future).get(); + this->client_->async_call("f002", use_future).get(); ASSERT_TRUE(this->server_->dispatcher()->has("f001")); ASSERT_TRUE(this->server_->dispatcher()->has("f002")); @@ -537,7 +489,8 @@ TYPED_TEST(Test, test_dispatcher) this->server_->dispatcher()->remove("f001"); ASSERT_THROW( - this->template future_call("f001").get(), packio_exception); + this->client_->async_call("f001", use_future).get(), + boost::system::system_error); ASSERT_FALSE(this->server_->dispatcher()->has("f001")); ASSERT_TRUE(this->server_->dispatcher()->has("f002")); @@ -587,8 +540,17 @@ TYPED_TEST(Test, test_end_of_work) ASSERT_TRUE(io.stopped()); // client runs out of work after a cancelled call + int cancelled_count{0}; io.restart(); - auto id = client->async_call("block", [](auto, auto) {}); + ASSERT_FALSE(io.stopped()); + id_type id; + client->async_call( + "block", + [&](auto ec, auto) { + ASSERT_TRUE(ec); + cancelled_count++; + }, + id); io.run_for(std::chrono::milliseconds{10}); ASSERT_FALSE(io.stopped()); client->cancel(id); @@ -597,13 +559,20 @@ TYPED_TEST(Test, test_end_of_work) // client runs out of work after multiple cancelled calls io.restart(); - client->async_call("block", [](auto, auto) {}); - client->async_call("block", [](auto, auto) {}); + client->async_call("block", [&](auto ec, auto) { + ASSERT_TRUE(ec); + cancelled_count++; + }); + client->async_call("block", [&](auto ec, auto) { + ASSERT_TRUE(ec); + cancelled_count++; + }); io.run_for(std::chrono::milliseconds{10}); ASSERT_FALSE(io.stopped()); client->cancel(); io.run_for(std::chrono::seconds{1}); ASSERT_TRUE(io.stopped()); + ASSERT_EQ(3, cancelled_count); } TYPED_TEST(Test, test_special_callables) @@ -746,14 +715,19 @@ TYPED_TEST(Test, test_errors) "add_sync", [](int a, int b) { return a + b; })); auto assert_error_message = - [&](std::string message, std::string procedure, auto... args) { - try { - this->template future_call(procedure, args...).get(); - ASSERT_FALSE(true); // never reached - } - catch (packio_exception& exc) { - ASSERT_EQ(message, exc.result_message()); - } + [&](std::string expected_message, std::string procedure, auto... args) { + std::promise p; + auto f = p.get_future(); + + this->client_->async_call( + procedure, + std::tuple{args...}, + [&](auto ec, msgpack::object_handle res) { + ASSERT_TRUE(ec); + p.set_value(res->as()); + }); + + ASSERT_EQ(expected_message, f.get()); }; assert_error_message(kErrorMessage, "error");
- + @@ -332,14 +344,14 @@

- + - + @@ -377,21 +389,21 @@

-

◆ async_notify() [2/2]

+ +

◆ async_notify() [2/2]

template<typename Protocol , template< class... > class Map = std::map>
-template<typename Buffer = msgpack::sbuffer, typename NotifyHandler >
+template<typename Buffer = msgpack::sbuffer, typename NotifyHandler = typename boost::asio::default_completion_token<executor_type>::type>
void packio::client< Protocol, Map >::async_notify auto packio::client< Protocol, Map >::async_notify ( std::string_view  name, std::tuple< Args... > const std::tuple< Args... > &  args,
NotifyHandler && handler handler = typename boost::asio::default_completion_token<executor_type>::type() 
-
- + @@ -400,7 +412,7 @@

- + diff --git a/docs/client_8h.html b/docs/client_8h.html index a50bafe..d79ae0e 100644 --- a/docs/client_8h.html +++ b/docs/client_8h.html @@ -76,10 +76,12 @@ More...

#include <atomic>
#include <chrono>
+#include <functional>
#include <map>
#include <memory>
#include <queue>
#include <string_view>
+#include <type_traits>
#include <boost/asio.hpp>
#include <msgpack.hpp>
#include "error_code.h"
diff --git a/docs/client_8h_source.html b/docs/client_8h_source.html index f97a2cb..1b79e7e 100644 --- a/docs/client_8h_source.html +++ b/docs/client_8h_source.html @@ -68,31 +68,31 @@
client.h
-Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_CLIENT_H
6 #define PACKIO_CLIENT_H
7 
10 
11 #include <atomic>
12 #include <chrono>
13 #include <map>
14 #include <memory>
15 #include <queue>
16 #include <string_view>
17 
18 #include <boost/asio.hpp>
19 #include <msgpack.hpp>
20 
21 #include "error_code.h"
22 #include "internal/manual_strand.h"
23 #include "internal/msgpack_rpc.h"
24 #include "internal/unique_function.h"
25 #include "internal/utils.h"
26 #include "traits.h"
27 
28 namespace packio {
29 
33 template <typename Protocol, template <class...> class Map = std::map>
34 class client : public std::enable_shared_from_this<client<Protocol, Map>> {
35 public:
36  using protocol_type = Protocol;
37  using socket_type = typename protocol_type::socket;
38  using executor_type =
39  typename socket_type::executor_type;
40  using std::enable_shared_from_this<client<Protocol, Map>>::shared_from_this;
41 
43  static constexpr size_t kDefaultBufferReserveSize = 4096;
44 
48  : socket_{std::move(socket)},
49  wstrand_{socket_.get_executor()},
50  call_strand_{socket_.get_executor()}
51  {
52  }
53 
55  socket_type& socket() noexcept { return socket_; }
57  const socket_type& socket() const noexcept { return socket_; }
58 
60  void set_buffer_reserve_size(std::size_t size) noexcept
61  {
62  buffer_reserve_size_ = size;
63  }
65  std::size_t get_buffer_reserve_size() const noexcept
66  {
67  return buffer_reserve_size_;
68  }
69 
71  executor_type get_executor() { return socket().get_executor(); }
72 
77  void cancel(id_type id)
78  {
79  boost::asio::dispatch(call_strand_, [this, self = shared_from_this(), id] {
80  auto ec = make_error_code(error::cancelled);
81  async_call_handler(
82  id, internal::make_msgpack_object(ec.message()), ec);
83  maybe_stop_reading();
84  });
85  }
86 
90  void cancel()
91  {
92  boost::asio::dispatch(call_strand_, [this, self = shared_from_this()] {
93  assert(call_strand_.running_in_this_thread());
94  auto ec = make_error_code(error::cancelled);
95  while (!pending_.empty()) {
96  async_call_handler(
97  pending_.begin()->first,
98  internal::make_msgpack_object(ec.message()),
99  ec);
100  }
101  maybe_stop_reading();
102  });
103  }
104 
116  template <typename Buffer = msgpack::sbuffer, typename NotifyHandler, typename... Args>
118  std::string_view name,
119  std::tuple<Args...> args,
120  NotifyHandler&& handler)
121  {
122  PACKIO_STATIC_ASSERT_TRAIT(NotifyHandler);
123  PACKIO_DEBUG("async_notify: {}", name);
124 
125  auto packer_buf = std::make_unique<Buffer>();
126  msgpack::pack(
127  *packer_buf,
128  std::forward_as_tuple(
129  static_cast<int>(msgpack_rpc_type::notification), name, args));
130 
131  async_send(
132  std::move(packer_buf),
133  [handler = std::forward<NotifyHandler>(handler)](
134  boost::system::error_code ec, std::size_t length) mutable {
135  if (ec) {
136  PACKIO_WARN("write error: {}", ec.message());
137  }
138  else {
139  PACKIO_TRACE("write: {}", length);
140  (void)length;
141  }
142 
143  handler(ec);
144  });
145  }
146 
149  template <typename Buffer = msgpack::sbuffer, typename NotifyHandler>
150  void async_notify(std::string_view name, NotifyHandler&& handler)
151  {
152  return async_notify<Buffer>(
153  name, std::tuple<>{}, std::forward<NotifyHandler>(handler));
154  }
155 
166  template <typename Buffer = msgpack::sbuffer, typename CallHandler, typename... Args>
168  std::string_view name,
169  std::tuple<Args...> args,
170  CallHandler&& handler)
171  {
172  PACKIO_STATIC_ASSERT_TRAIT(CallHandler);
173  PACKIO_DEBUG("async_call: {}", name);
174 
175  auto id = id_.fetch_add(1, std::memory_order_acq_rel);
176  auto packer_buf = std::make_unique<Buffer>();
177  msgpack::pack(
178  *packer_buf,
179  std::forward_as_tuple(
180  static_cast<int>(msgpack_rpc_type::request), id, name, args));
181 
182  boost::asio::dispatch(
183  call_strand_,
184  [this,
185  self = shared_from_this(),
186  id,
187  handler = std::forward<CallHandler>(handler),
188  packer_buf = std::move(packer_buf)]() mutable {
189  // we must emplace the id and handler before sending data
190  // otherwise we might drop a fast response
191  assert(call_strand_.running_in_this_thread());
192  pending_.try_emplace(id, std::forward<CallHandler>(handler));
193 
194  // if we are not reading, start the read operation
195  if (!reading_) {
196  PACKIO_DEBUG("start reading");
197  async_read(std::make_unique<msgpack::unpacker>());
198  }
199 
200  // send the request buffer
201  async_send(
202  std::move(packer_buf),
203  [this, self = std::move(self), id](
204  boost::system::error_code ec, std::size_t length) mutable {
205  if (ec) {
206  PACKIO_WARN("write error: {}", ec.message());
207  async_call_handler(
208  id,
209  internal::make_msgpack_object(ec.message()),
210  ec);
211  }
212  else {
213  PACKIO_TRACE("write: {}", length);
214  (void)length;
215  }
216  });
217  });
218 
219  return id;
220  }
221 
224  template <typename Buffer = msgpack::sbuffer, typename CallHandler>
225  id_type async_call(std::string_view name, CallHandler&& handler)
226  {
227  return async_call<Buffer>(
228  name, std::tuple<>{}, std::forward<CallHandler>(handler));
229  }
230 
231 private:
232  using async_call_handler_type = internal::unique_function<
233  void(boost::system::error_code, msgpack::object_handle)>;
234 
235  void maybe_stop_reading()
236  {
237  assert(call_strand_.running_in_this_thread());
238  if (reading_ && pending_.empty()) {
239  PACKIO_DEBUG("stop reading");
240  boost::system::error_code ec;
241  socket_.cancel(ec);
242  if (ec) {
243  PACKIO_WARN("cancel failed: {}", ec.message());
244  }
245  }
246  }
247 
248  template <typename Buffer, typename WriteHandler>
249  void async_send(std::unique_ptr<Buffer> buffer_ptr, WriteHandler&& handler)
250  {
251  wstrand_.push([this,
252  self = shared_from_this(),
253  buffer_ptr = std::move(buffer_ptr),
254  handler = std::forward<WriteHandler>(handler)]() mutable {
255  using internal::buffer;
256 
257  internal::set_no_delay(socket_);
258 
259  auto buf = buffer(*buffer_ptr);
260  boost::asio::async_write(
261  socket_,
262  buf,
263  [this,
264  self = std::move(self),
265  buffer_ptr = std::move(buffer_ptr),
266  handler = std::forward<WriteHandler>(handler)](
267  boost::system::error_code ec, size_t length) mutable {
268  wstrand_.next();
269  handler(ec, length);
270  });
271  });
272  }
273 
274  void async_read(std::unique_ptr<msgpack::unpacker> unpacker)
275  {
276  unpacker->reserve_buffer(buffer_reserve_size_);
277  auto buffer = boost::asio::buffer(
278  unpacker->buffer(), unpacker->buffer_capacity());
279 
280  assert(call_strand_.running_in_this_thread());
281  reading_ = true;
282  PACKIO_TRACE("reading ... {} call(s) pending", pending_.size());
283  socket_.async_read_some(
284  buffer,
285  boost::asio::bind_executor(
286  call_strand_,
287  [this, self = shared_from_this(), unpacker = std::move(unpacker)](
288  boost::system::error_code ec, size_t length) mutable {
289  PACKIO_TRACE("read: {}", length);
290  unpacker->buffer_consumed(length);
291 
292  msgpack::object_handle response;
293  while (unpacker->next(response)) {
294  process_response(std::move(response), ec);
295  }
296 
297  // stop if there is an error or there is no more pending calls
298  assert(call_strand_.running_in_this_thread());
299 
300  if (ec && ec != boost::asio::error::operation_aborted) {
301  PACKIO_WARN("read error: {}", ec.message());
302  reading_ = false;
303  return;
304  }
305 
306  if (pending_.empty()) {
307  PACKIO_TRACE("done reading, no more pending calls");
308  reading_ = false;
309  return;
310  }
311 
312  async_read(std::move(unpacker));
313  }));
314  }
315 
316  void process_response(msgpack::object_handle response, boost::system::error_code ec)
317  {
318  if (!verify_reponse(response.get())) {
319  PACKIO_ERROR("received unexpected response");
320  return;
321  }
322 
323  const auto& call_response = response->via.array.ptr;
324  int id = call_response[1].as<int>();
325  msgpack::object err = call_response[2];
326  msgpack::object result = call_response[3];
327 
328  if (err.type != msgpack::type::NIL) {
329  ec = make_error_code(error::call_error);
330  async_call_handler(id, {err, std::move(response.zone())}, ec);
331  }
332  else {
333  ec = make_error_code(error::success);
334  async_call_handler(id, {result, std::move(response.zone())}, ec);
335  }
336  }
337 
338  void async_call_handler(
339  id_type id,
340  msgpack::object_handle result,
341  boost::system::error_code ec)
342  {
343  boost::asio::dispatch(
344  call_strand_, [this, ec, id, result = std::move(result)]() mutable {
345  PACKIO_DEBUG("calling handler for id: {}", id);
346 
347  assert(call_strand_.running_in_this_thread());
348  auto it = pending_.find(id);
349  if (it == pending_.end()) {
350  PACKIO_WARN("unexisting id");
351  return;
352  }
353 
354  auto handler = std::move(it->second);
355  pending_.erase(it);
356 
357  // handle the response asynchronously (post)
358  // to schedule the next read immediately
359  // this will allow parallel response handling
360  // in multi-threaded environments
361  boost::asio::post(
362  socket_.get_executor(),
363  [ec,
364  handler = std::move(handler),
365  result = std::move(result)]() mutable {
366  handler(ec, std::move(result));
367  });
368  });
369  }
370 
371  bool verify_reponse(const msgpack::object& response)
372  {
373  if (response.type != msgpack::type::ARRAY) {
374  PACKIO_ERROR("unexpected message type: {}", response.type);
375  return false;
376  }
377  if (response.via.array.size != 4) {
378  PACKIO_ERROR("unexpected message size: {}", response.via.array.size);
379  return false;
380  }
381  int type = response.via.array.ptr[0].as<int>();
382  if (type != static_cast<int>(msgpack_rpc_type::response)) {
383  PACKIO_ERROR("unexpected type: {}", type);
384  return false;
385  }
386  return true;
387  }
388 
389  socket_type socket_;
390  std::size_t buffer_reserve_size_{kDefaultBufferReserveSize};
391  std::atomic<id_type> id_{0};
392 
393  internal::manual_strand<executor_type> wstrand_;
394 
395  boost::asio::strand<executor_type> call_strand_;
396  Map<id_type, async_call_handler_type> pending_;
397  bool reading_{false};
398 };
399 
400 } // packio
401 
402 #endif // PACKIO_CLIENT_H
msgpack-RPC related types and values
+Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_CLIENT_H
6 #define PACKIO_CLIENT_H
7 
10 
11 #include <atomic>
12 #include <chrono>
13 #include <functional>
14 #include <map>
15 #include <memory>
16 #include <queue>
17 #include <string_view>
18 #include <type_traits>
19 
20 #include <boost/asio.hpp>
21 #include <msgpack.hpp>
22 
23 #include "error_code.h"
24 #include "internal/manual_strand.h"
25 #include "internal/msgpack_rpc.h"
26 #include "internal/unique_function.h"
27 #include "internal/utils.h"
28 #include "traits.h"
29 
30 namespace packio {
31 
35 template <typename Protocol, template <class...> class Map = std::map>
36 class client : public std::enable_shared_from_this<client<Protocol, Map>> {
37 public:
38  using protocol_type = Protocol;
39  using socket_type = typename protocol_type::socket;
40  using executor_type =
41  typename socket_type::executor_type;
42  using std::enable_shared_from_this<client<Protocol, Map>>::shared_from_this;
43 
45  static constexpr size_t kDefaultBufferReserveSize = 4096;
46 
50  : socket_{std::move(socket)},
51  wstrand_{socket_.get_executor()},
52  call_strand_{socket_.get_executor()}
53  {
54  }
55 
57  socket_type& socket() noexcept { return socket_; }
59  const socket_type& socket() const noexcept { return socket_; }
60 
62  void set_buffer_reserve_size(std::size_t size) noexcept
63  {
64  buffer_reserve_size_ = size;
65  }
67  std::size_t get_buffer_reserve_size() const noexcept
68  {
69  return buffer_reserve_size_;
70  }
71 
73  executor_type get_executor() { return socket().get_executor(); }
74 
79  void cancel(id_type id)
80  {
81  boost::asio::dispatch(call_strand_, [self = shared_from_this(), id] {
82  auto ec = make_error_code(error::cancelled);
83  self->async_call_handler(
84  id, internal::make_msgpack_object(ec.message()), ec);
85  self->maybe_stop_reading();
86  });
87  }
88 
92  void cancel()
93  {
94  boost::asio::dispatch(call_strand_, [self = shared_from_this()] {
95  assert(self->call_strand_.running_in_this_thread());
96  auto ec = make_error_code(error::cancelled);
97  while (!self->pending_.empty()) {
98  self->async_call_handler(
99  self->pending_.begin()->first,
100  internal::make_msgpack_object(ec.message()),
101  ec);
102  }
103  self->maybe_stop_reading();
104  });
105  }
106 
118  template <
119  typename Buffer = msgpack::sbuffer,
120  typename NotifyHandler =
121  typename boost::asio::default_completion_token<executor_type>::type,
122  typename... Args>
124  std::string_view name,
125  const std::tuple<Args...>& args,
126  NotifyHandler&& handler =
127  typename boost::asio::default_completion_token<executor_type>::type())
128  {
129  return boost::asio::async_initiate<NotifyHandler, void(boost::system::error_code)>(
130  initiate_async_notify<Buffer>(this), handler, name, args);
131  }
132 
135  template <
136  typename Buffer = msgpack::sbuffer,
137  typename NotifyHandler =
138  typename boost::asio::default_completion_token<executor_type>::type>
140  std::string_view name,
141  NotifyHandler&& handler =
142  typename boost::asio::default_completion_token<executor_type>::type())
143  {
144  return async_notify<Buffer>(
145  name, std::tuple{}, std::forward<NotifyHandler>(handler));
146  }
147 
158  template <
159  typename Buffer = msgpack::sbuffer,
160  typename CallHandler =
161  typename boost::asio::default_completion_token<executor_type>::type,
162  typename... Args>
164  std::string_view name,
165  const std::tuple<Args...>& args,
166  CallHandler&& handler =
167  typename boost::asio::default_completion_token<executor_type>::type(),
168  std::optional<std::reference_wrapper<id_type>> call_id = std::nullopt)
169  {
170  id_type tmp_id;
171  return boost::asio::async_initiate<
172  CallHandler,
173  void(boost::system::error_code, msgpack::object_handle)>(
174  initiate_async_call<Buffer>(this),
175  handler,
176  name,
177  args,
178  call_id.value_or(tmp_id));
179  }
180 
183  template <
184  typename Buffer = msgpack::sbuffer,
185  typename CallHandler =
186  typename boost::asio::default_completion_token<executor_type>::type>
188  std::string_view name,
189  CallHandler&& handler =
190  typename boost::asio::default_completion_token<executor_type>::type(),
191  std::optional<std::reference_wrapper<id_type>> call_id = std::nullopt)
192  {
193  return async_call<Buffer>(
194  name, std::tuple{}, std::forward<CallHandler>(handler), call_id);
195  }
196 
197 private:
198  using async_call_handler_type = internal::unique_function<
199  void(boost::system::error_code, msgpack::object_handle)>;
200 
201  void maybe_stop_reading()
202  {
203  assert(call_strand_.running_in_this_thread());
204  if (reading_ && pending_.empty()) {
205  PACKIO_DEBUG("stop reading");
206  boost::system::error_code ec;
207  socket_.cancel(ec);
208  if (ec) {
209  PACKIO_WARN("cancel failed: {}", ec.message());
210  }
211  }
212  }
213 
214  template <typename Buffer, typename WriteHandler>
215  void async_send(std::unique_ptr<Buffer> buffer_ptr, WriteHandler&& handler)
216  {
217  wstrand_.push([this,
218  self = shared_from_this(),
219  buffer_ptr = std::move(buffer_ptr),
220  handler = std::forward<WriteHandler>(handler)]() mutable {
221  using internal::buffer;
222 
223  internal::set_no_delay(socket_);
224 
225  auto buf = buffer(*buffer_ptr);
226  boost::asio::async_write(
227  socket_,
228  buf,
229  [self = std::move(self),
230  buffer_ptr = std::move(buffer_ptr),
231  handler = std::forward<WriteHandler>(handler)](
232  boost::system::error_code ec, size_t length) mutable {
233  self->wstrand_.next();
234  handler(ec, length);
235  });
236  });
237  }
238 
239  void async_read(std::unique_ptr<msgpack::unpacker> unpacker)
240  {
241  unpacker->reserve_buffer(buffer_reserve_size_);
242  auto buffer = boost::asio::buffer(
243  unpacker->buffer(), unpacker->buffer_capacity());
244 
245  assert(call_strand_.running_in_this_thread());
246  reading_ = true;
247  PACKIO_TRACE("reading ... {} call(s) pending", pending_.size());
248  socket_.async_read_some(
249  buffer,
250  boost::asio::bind_executor(
251  call_strand_,
252  [self = shared_from_this(), unpacker = std::move(unpacker)](
253  boost::system::error_code ec, size_t length) mutable {
254  PACKIO_TRACE("read: {}", length);
255  unpacker->buffer_consumed(length);
256 
257  msgpack::object_handle response;
258  while (unpacker->next(response)) {
259  self->process_response(std::move(response), ec);
260  }
261 
262  // stop if there is an error or there is no more pending calls
263  assert(self->call_strand_.running_in_this_thread());
264 
265  if (ec && ec != boost::asio::error::operation_aborted) {
266  PACKIO_WARN("read error: {}", ec.message());
267  self->reading_ = false;
268  return;
269  }
270 
271  if (self->pending_.empty()) {
272  PACKIO_TRACE("done reading, no more pending calls");
273  self->reading_ = false;
274  return;
275  }
276 
277  self->async_read(std::move(unpacker));
278  }));
279  }
280 
281  void process_response(msgpack::object_handle response, boost::system::error_code ec)
282  {
283  if (!verify_reponse(response.get())) {
284  PACKIO_ERROR("received unexpected response");
285  return;
286  }
287 
288  const auto& call_response = response->via.array.ptr;
289  int id = call_response[1].as<int>();
290  msgpack::object err = call_response[2];
291  msgpack::object result = call_response[3];
292 
293  if (err.type != msgpack::type::NIL) {
294  ec = make_error_code(error::call_error);
295  async_call_handler(id, {err, std::move(response.zone())}, ec);
296  }
297  else {
298  ec = make_error_code(error::success);
299  async_call_handler(id, {result, std::move(response.zone())}, ec);
300  }
301  }
302 
303  void async_call_handler(
304  id_type id,
305  msgpack::object_handle result,
306  boost::system::error_code ec)
307  {
308  boost::asio::dispatch(
309  call_strand_, [this, ec, id, result = std::move(result)]() mutable {
310  PACKIO_DEBUG("calling handler for id: {}", id);
311 
312  assert(call_strand_.running_in_this_thread());
313  auto it = pending_.find(id);
314  if (it == pending_.end()) {
315  PACKIO_WARN("unexisting id");
316  return;
317  }
318 
319  auto handler = std::move(it->second);
320  pending_.erase(it);
321 
322  // handle the response asynchronously (post)
323  // to schedule the next read immediately
324  // this will allow parallel response handling
325  // in multi-threaded environments
326  boost::asio::post(
327  socket_.get_executor(),
328  [ec,
329  handler = std::move(handler),
330  result = std::move(result)]() mutable {
331  handler(ec, std::move(result));
332  });
333  });
334  }
335 
336  bool verify_reponse(const msgpack::object& response)
337  {
338  if (response.type != msgpack::type::ARRAY) {
339  PACKIO_ERROR("unexpected message type: {}", response.type);
340  return false;
341  }
342  if (response.via.array.size != 4) {
343  PACKIO_ERROR("unexpected message size: {}", response.via.array.size);
344  return false;
345  }
346  int type = response.via.array.ptr[0].as<int>();
347  if (type != static_cast<int>(msgpack_rpc_type::response)) {
348  PACKIO_ERROR("unexpected type: {}", type);
349  return false;
350  }
351  return true;
352  }
353 
354  template <typename Buffer>
355  class initiate_async_notify {
356  public:
357  using executor_type = typename client::executor_type;
358 
359  explicit initiate_async_notify(client* self) : self_(self) {}
360 
361  executor_type get_executor() const noexcept
362  {
363  return self_->get_executor();
364  }
365 
366  template <typename NotifyHandler, typename... Args>
367  void operator()(
368  NotifyHandler&& handler,
369  std::string_view name,
370  const std::tuple<Args...>& args) const
371  {
372  PACKIO_STATIC_ASSERT_TRAIT(NotifyHandler);
373  PACKIO_DEBUG("async_notify: {}", name);
374 
375  auto packer_buf = std::make_unique<Buffer>();
376  msgpack::pack(
377  *packer_buf,
378  std::forward_as_tuple(
379  static_cast<int>(msgpack_rpc_type::notification), name, args));
380 
381  self_->async_send(
382  std::move(packer_buf),
383  [handler = std::forward<NotifyHandler>(handler)](
384  boost::system::error_code ec, std::size_t length) mutable {
385  if (ec) {
386  PACKIO_WARN("write error: {}", ec.message());
387  }
388  else {
389  PACKIO_TRACE("write: {}", length);
390  (void)length;
391  }
392 
393  handler(ec);
394  });
395  }
396 
397  private:
398  client* self_;
399  };
400 
401  template <typename Buffer>
402  class initiate_async_call {
403  public:
404  using executor_type = typename client::executor_type;
405 
406  explicit initiate_async_call(client* self) : self_(self) {}
407 
408  executor_type get_executor() const noexcept
409  {
410  return self_->get_executor();
411  }
412 
413  template <typename CallHandler, typename... Args>
414  void operator()(
415  CallHandler&& handler,
416  std::string_view name,
417  const std::tuple<Args...>& args,
418  id_type& call_id) const
419  {
420  PACKIO_STATIC_ASSERT_TRAIT(CallHandler);
421  PACKIO_DEBUG("async_call: {}", name);
422 
423  call_id = self_->id_.fetch_add(1, std::memory_order_acq_rel);
424  auto packer_buf = std::make_unique<Buffer>();
425  msgpack::pack(
426  *packer_buf,
427  std::forward_as_tuple(
428  static_cast<int>(msgpack_rpc_type::request),
429  call_id,
430  name,
431  args));
432 
433  boost::asio::dispatch(
434  self_->call_strand_,
435  [self = self_->shared_from_this(),
436  call_id,
437  handler = internal::wrap_call_handler(
438  std::forward<CallHandler>(handler)),
439  packer_buf = std::move(packer_buf)]() mutable {
440  // we must emplace the id and handler before sending data
441  // otherwise we might drop a fast response
442  assert(self->call_strand_.running_in_this_thread());
443  self->pending_.try_emplace(call_id, std::move(handler));
444 
445  // if we are not reading, start the read operation
446  if (!self->reading_) {
447  PACKIO_DEBUG("start reading");
448  self->async_read(std::make_unique<msgpack::unpacker>());
449  }
450 
451  // send the request buffer
452  self->async_send(
453  std::move(packer_buf),
454  [self = std::move(self), call_id](
455  boost::system::error_code ec,
456  std::size_t length) mutable {
457  if (ec) {
458  PACKIO_WARN("write error: {}", ec.message());
459  self->async_call_handler(
460  call_id,
461  internal::make_msgpack_object(ec.message()),
462  ec);
463  }
464  else {
465  PACKIO_TRACE("write: {}", length);
466  (void)length;
467  }
468  });
469  });
470  }
471 
472  private:
473  client* self_;
474  };
475 
476  socket_type socket_;
477  std::size_t buffer_reserve_size_{kDefaultBufferReserveSize};
478  std::atomic<id_type> id_{0};
479 
480  internal::manual_strand<executor_type> wstrand_;
481 
482  boost::asio::strand<executor_type> call_strand_;
483  Map<id_type, async_call_handler_type> pending_;
484  bool reading_{false};
485 };
486 
487 } // packio
488 
489 #endif // PACKIO_CLIENT_H
msgpack-RPC related types and values
An error happened during the call.
-
socket_type & socket() noexcept
Get the underlying socket.
Definition: client.h:55
-
client(socket_type socket)
The constructor.
Definition: client.h:47
-
typename socket_type::executor_type executor_type
The executor type.
Definition: client.h:39
-
typename protocol_type::socket socket_type
The socket type.
Definition: client.h:37
-
void set_buffer_reserve_size(std::size_t size) noexcept
Set the size reserved by the reception buffer.
Definition: client.h:60
-
id_type async_call(std::string_view name, CallHandler &&handler)
Call a remote procedure.
Definition: client.h:225
+
socket_type & socket() noexcept
Get the underlying socket.
Definition: client.h:57
+
client(socket_type socket)
The constructor.
Definition: client.h:49
+
typename socket_type::executor_type executor_type
The executor type.
Definition: client.h:41
+
auto async_notify(std::string_view name, const std::tuple< Args... > &args, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())
Send a notify request to the server with argument.
Definition: client.h:123
+
typename protocol_type::socket socket_type
The socket type.
Definition: client.h:39
+
auto async_call(std::string_view name, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)
Call a remote procedure.
Definition: client.h:187
+
void set_buffer_reserve_size(std::size_t size) noexcept
Set the size reserved by the reception buffer.
Definition: client.h:62
Traits definition.
-
The client class.
Definition: client.h:34
-
std::size_t get_buffer_reserve_size() const noexcept
Get the size reserved by the reception buffer.
Definition: client.h:65
-
void async_notify(std::string_view name, std::tuple< Args... > args, NotifyHandler &&handler)
Send a notify request to the server with argument.
Definition: client.h:117
-
void cancel(id_type id)
Cancel a pending call.
Definition: client.h:77
+
The client class.
Definition: client.h:36
+
std::size_t get_buffer_reserve_size() const noexcept
Get the size reserved by the reception buffer.
Definition: client.h:67
+
void cancel(id_type id)
Cancel a pending call.
Definition: client.h:79
+
auto async_notify(std::string_view name, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())
Send a notify request to the server with no argument.
Definition: client.h:139
Enum error.
-
id_type async_call(std::string_view name, std::tuple< Args... > args, CallHandler &&handler)
Call a remote procedure.
Definition: client.h:167
-
void async_notify(std::string_view name, NotifyHandler &&handler)
Send a notify request to the server with no argument.
Definition: client.h:150
-
The packio namespace.
Definition: as.h:16
+
auto async_call(std::string_view name, const std::tuple< Args... > &args, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)
Call a remote procedure.
Definition: client.h:163
+
The packio namespace.
Definition: client.h:30
uint32_t id_type
Type used to store call IDs.
Definition: msgpack_rpc.h:16
-
void cancel()
Cancel all pending calls.
Definition: client.h:90
-
const socket_type & socket() const noexcept
Get the underlying socket, const.
Definition: client.h:57
+
void cancel()
Cancel all pending calls.
Definition: client.h:92
+
const socket_type & socket() const noexcept
Get the underlying socket, const.
Definition: client.h:59
The operation has been cancelled.
-
static constexpr size_t kDefaultBufferReserveSize
The default size reserved by the reception buffer.
Definition: client.h:43
-
executor_type get_executor()
Get the executor associated with the object.
Definition: client.h:71
-
Protocol protocol_type
The protocol type.
Definition: client.h:36
+
static constexpr size_t kDefaultBufferReserveSize
The default size reserved by the reception buffer.
Definition: client.h:45
+
executor_type get_executor()
Get the executor associated with the object.
Definition: client.h:73
+
Protocol protocol_type
The protocol type.
Definition: client.h:38
diff --git a/docs/dir_7f2d0a2a0928023e758bb1e0e178d043.html b/docs/dir_7f2d0a2a0928023e758bb1e0e178d043.html index 044f99e..1b442e5 100644 --- a/docs/dir_7f2d0a2a0928023e758bb1e0e178d043.html +++ b/docs/dir_7f2d0a2a0928023e758bb1e0e178d043.html @@ -78,9 +78,6 @@
void packio::client< Protocol, Map >::async_notify auto packio::client< Protocol, Map >::async_notify ( std::string_view  name, NotifyHandler && handler handler = typename boost::asio::default_completion_token<executor_type>::type() 
- - - diff --git a/docs/dispatcher_8h_source.html b/docs/dispatcher_8h_source.html index 3297e46..7dd7866 100644 --- a/docs/dispatcher_8h_source.html +++ b/docs/dispatcher_8h_source.html @@ -79,7 +79,7 @@
The dispatcher class, used to store and dispatch procedures.
Definition: dispatcher.h:32
bool add_async(std::string_view name, AsyncProcedure &&fct)
Add an asynchronous procedure to the dispatcher.
Definition: dispatcher.h:59
void set_error(T &&error_value)
Notify erroneous completion of the procedure with an associated error.
Definition: handler.h:70
-
The packio namespace.
Definition: as.h:16
+
The packio namespace.
Definition: client.h:30
bool add(std::string_view name, SyncProcedure &&fct)
Add a synchronous procedure to the dispatcher.
Definition: dispatcher.h:46
Class completion_handler.
std::vector< std::string > known() const
Get the name of all known procedures.
Definition: dispatcher.h:98
diff --git a/docs/error__code_8h_source.html b/docs/error__code_8h_source.html index b25c26f..21972db 100644 --- a/docs/error__code_8h_source.html +++ b/docs/error__code_8h_source.html @@ -75,7 +75,7 @@
The procedure name is unknown, server-side error.
error
The error codes enumeration.
Definition: error_code.h:17
Definition: error_code.h:57
-
The packio namespace.
Definition: as.h:16
+
The packio namespace.
Definition: client.h:30
The operation has been cancelled.
diff --git a/docs/files.html b/docs/files.html index e47e819..e2f7fa6 100644 --- a/docs/files.html +++ b/docs/files.html @@ -73,15 +73,14 @@ - - - - - - - - - + + + + + + + +

Files

file  as.h [code]
 Function as.
 
file  client.h [code]
 Class client.
 
 msgpack_rpc.hMsgpack-RPC related types and values
 unique_function.h
 utils.h
 as.hFunction as
 client.hClass client
 dispatcher.hClass dispatcher
 error_code.hEnum error
 handler.hClass completion_handler
 packio.hMain include file
 server.hClass server
 server_session.hClass server_session
 traits.hTraits definition
 client.hClass client
 dispatcher.hClass dispatcher
 error_code.hEnum error
 handler.hClass completion_handler
 packio.hMain include file
 server.hClass server
 server_session.hClass server_session
 traits.hTraits definition
diff --git a/docs/functions.html b/docs/functions.html index da187ec..61cdad3 100644 --- a/docs/functions.html +++ b/docs/functions.html @@ -76,10 +76,10 @@

- a -

    : packio::dispatcher< Map, Lockable >
  • async_call() -: packio::client< Protocol, Map > +: packio::client< Protocol, Map >
  • async_notify() -: packio::client< Protocol, Map > +: packio::client< Protocol, Map >
  • async_serve() : packio::server< Protocol, Dispatcher > diff --git a/docs/functions_func.html b/docs/functions_func.html index b053213..221078e 100644 --- a/docs/functions_func.html +++ b/docs/functions_func.html @@ -73,10 +73,10 @@

    - a -

      : packio::dispatcher< Map, Lockable >
    • async_call() -: packio::client< Protocol, Map > +: packio::client< Protocol, Map >
    • async_notify() -: packio::client< Protocol, Map > +: packio::client< Protocol, Map >
    • async_serve() : packio::server< Protocol, Dispatcher > diff --git a/docs/handler_8h_source.html b/docs/handler_8h_source.html index 14baacc..f27cd30 100644 --- a/docs/handler_8h_source.html +++ b/docs/handler_8h_source.html @@ -79,7 +79,7 @@
      void set_error()
      Notify erroneous completion of the procedure without an error value.
      Definition: handler.h:79
      error
      The error codes enumeration.
      Definition: error_code.h:17
      void set_error(T &&error_value)
      Notify erroneous completion of the procedure with an associated error.
      Definition: handler.h:70
      -
      The packio namespace.
      Definition: as.h:16
      +
      The packio namespace.
      Definition: client.h:30
      void operator()(T &&return_value)
      Same as set_value.
      Definition: handler.h:83
      diff --git a/docs/index.html b/docs/index.html index 55f3c7d..d67a182 100644 --- a/docs/index.html +++ b/docs/index.html @@ -64,15 +64,14 @@
      Header-only | msgpack-RPC | Boost.Asio
      -

      This library requires C++17 and is designed as an extension to Boost.Asio. It will let you built asynchronous servers or client for msgpack-RPC.

      -

      The library is still under development and is therefore subject heavy API changes.

      -

      The project is hosted on GitHub and packaged with Conan. Documentation is available on GitHub Pages.

      +

      This library requires C++17 and is designed as an extension to Boost.Asio. It will let you build asynchronous servers or client for msgpack-RPC.

      +

      The project is hosted on GitHub and available on Conan Center. Documentation is available on GitHub Pages.

      Primer

      -
      // Declare a server and a client, sharing the same io_context
      boost::asio::io_context io;
      ip::tcp::endpoint bind_ep{ip::make_address("127.0.0.1"), 0};
      auto server = std::make_shared<packio::server<ip::tcp>>(ip::tcp::acceptor{io, bind_ep});
      auto client = std::make_shared<packio::client<ip::tcp>>(ip::tcp::socket{io});
      // Declare a synchronous callback
      server->dispatcher()->add("add", [](int a, int b) { return a + b; });
      // Declare an asynchronous callback
      server->dispatcher()->add_async(
      "multiply", [](packio::completion_handler complete, int a, int b) {
      complete(a * b);
      });
      // Accept connections forever
      server->async_serve_forever();
      // Connect the client
      client->socket().connect(server.acceptor().local_endpoint());
      // Make an asynchronous call
      client->async_call("add", std::make_tuple(42, 24),
      [&](boost::system::error_code, msgpack::object r) {
      std::cout << "The result is: " << r.as<int>() << std::endl;
      });
      // Use as<> wrapper to hide result type conversion
      client->async_call(
      "multiply",
      std::make_tuple(42, 24),
      packio::as<int>([&](boost::system::error_code, std::optional<int> r) {
      multiply_result.set_value(*r);
      }));

      Requirements

      +
      // Declare a server and a client, sharing the same io_context
      boost::asio::io_context io;
      ip::tcp::endpoint bind_ep{ip::make_address("127.0.0.1"), 0};
      auto server = std::make_shared<packio::server<ip::tcp>>(ip::tcp::acceptor{io, bind_ep});
      auto client = std::make_shared<packio::client<ip::tcp>>(ip::tcp::socket{io});
      // Declare a synchronous callback
      server->dispatcher()->add("add", [](int a, int b) { return a + b; });
      // Declare an asynchronous callback
      server->dispatcher()->add_async(
      "multiply", [](packio::completion_handler complete, int a, int b) {
      // Call the completion handler later
      boost::asio::post(
      io, [a, b, complete = std::move(complete)]() mutable {
      complete(a * b);
      });
      });
      // Accept connections forever
      server->async_serve_forever();
      // Connect the client
      client->socket().connect(server.acceptor().local_endpoint());
      // Make an asynchronous call
      client->async_call("add", std::make_tuple(42, 24),
      [&](boost::system::error_code, msgpack::object r) {
      std::cout << "The result is: " << r.as<int>() << std::endl;
      });
      // Use boost::asio::use_future
      std::future<msgpack::object_handle> add_future = client->async_call(
      "add", std::tuple{12, 23}, boost::asio::use_future);
      std::cout << "The result is: " << add_future.get()->as<int>() << std::endl;
      // Use auto result type conversion
      client->async_call(
      "multiply",
      std::make_tuple(42, 24),
      [&](boost::system::error_code, std::optional<int> r) {
      std::cout << "The result is: " << *r << std::endl;
      });

      Requirements

      • C++17
      • -
      • Boost.Asio >= 1.70.0
      • -
      • msgpack >= 3.0.1
      • +
      • Boost.Asio >= 1.72.0
      • +
      • msgpack >= 3.2.1

      Tested compilers

        @@ -89,9 +88,9 @@

        Tested compilers

      • Visual Studio 2019 Version 16

      Conan

      -

      Add my remote and install packio:

      conan remote add qchateau https://api.bintray.com/conan/qchateau/qchateau
      conan install -r qchateau packio/0.8.0

      Bonus

      +
      conan install packio/1.1.0

      Bonus

      Let's compute fibonacci's numbers recursively using packio on a single thread.

      -
      #include <iostream>
      #include <boost/asio.hpp>
      #include <packio/packio.h>
      namespace ip = boost::asio::ip;
      int main(int argc, char** argv)
      {
      if (argc < 2) {
      std::cerr << "I require one argument" << std::endl;
      return 1;
      }
      const int n = std::atoi(argv[1]);
      boost::asio::io_context io;
      ip::tcp::endpoint bind_ep{ip::make_address("127.0.0.1"), 0};
      auto server = std::make_shared<packio::server<ip::tcp>>(
      ip::tcp::acceptor{io, bind_ep});
      auto client = std::make_shared<packio::client<ip::tcp>>(ip::tcp::socket{io});
      server->dispatcher()->add_async(
      "fibonacci", [&](packio::completion_handler complete, int n) {
      if (n <= 1) {
      complete(n);
      return;
      }
      client->async_call(
      "fibonacci",
      std::tuple{n - 1},
      [=, &client, complete = std::move(complete)](
      boost::system::error_code,
      msgpack::object_handle result1) mutable {
      int r1 = result1->as<int>();
      client->async_call(
      "fibonacci",
      std::tuple{n - 2},
      [=, complete = std::move(complete)](
      boost::system::error_code,
      msgpack::object_handle result2) mutable {
      int r2 = result2->as<int>();
      complete(r1 + r2);
      });
      });
      });
      client->socket().connect(server->acceptor().local_endpoint());
      server->async_serve_forever();
      int result = 0;
      client->async_call(
      "fibonacci",
      std::make_tuple(n),
      [&](boost::system::error_code, msgpack::object_handle r) {
      result = r->as<int>();
      io.stop();
      });
      io.run();
      std::cout << "F{" << n << "} = " << result << std::endl;
      return 0;
      }
      +
      #include <iostream>
      #include <boost/asio.hpp>
      #include <packio/packio.h>
      namespace ip = boost::asio::ip;
      int main(int argc, char** argv)
      {
      if (argc < 2) {
      std::cerr << "I require one argument" << std::endl;
      return 1;
      }
      const int n = std::atoi(argv[1]);
      boost::asio::io_context io;
      ip::tcp::endpoint bind_ep{ip::make_address("127.0.0.1"), 0};
      auto server = std::make_shared<packio::server<ip::tcp>>(
      ip::tcp::acceptor{io, bind_ep});
      auto client = std::make_shared<packio::client<ip::tcp>>(ip::tcp::socket{io});
      server->dispatcher()->add_async(
      "fibonacci", [&](packio::completion_handler complete, int n) {
      if (n <= 1) {
      complete(n);
      return;
      }
      client->async_call(
      "fibonacci",
      std::tuple{n - 1},
      [n, &client, complete = std::move(complete)](
      boost::system::error_code, std::optional<int> r1) mutable {
      client->async_call(
      "fibonacci",
      std::tuple{n - 2},
      [r1, complete = std::move(complete)](
      boost::system::error_code,
      std::optional<int> r2) mutable {
      complete(*r1 + *r2);
      });
      });
      });
      client->socket().connect(server->acceptor().local_endpoint());
      server->async_serve_forever();
      int result = 0;
      client->async_call(
      "fibonacci",
      std::tuple{n},
      [&](boost::system::error_code, std::optional<int> r) {
      result = *r;
      io.stop();
      });
      io.run();
      std::cout << "F{" << n << "} = " << result << std::endl;
      return 0;
      }
      diff --git a/docs/manual__strand_8h_source.html b/docs/manual__strand_8h_source.html index ae022ec..783995a 100644 --- a/docs/manual__strand_8h_source.html +++ b/docs/manual__strand_8h_source.html @@ -68,7 +68,7 @@
      manual_strand.h
      -
      1 // This Source Code Form is subject to the terms of the Mozilla Public
      2 // License, v. 2.0. If a copy of the MPL was not distributed with this
      3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
      4 
      5 #ifndef PACKIO_MANUAL_STRAND_H
      6 #define PACKIO_MANUAL_STRAND_H
      7 
      8 #include <queue>
      9 #include <boost/asio.hpp>
      10 
      11 #include "unique_function.h"
      12 
      13 namespace packio {
      14 namespace internal {
      15 
      16 template <typename Executor>
      17 class manual_strand {
      18 public:
      19  using function_type = unique_function<void()>;
      20 
      21  manual_strand(const Executor& executor) : strand_{executor} {}
      22 
      23  void push(function_type function)
      24  {
      25  boost::asio::dispatch(
      26  strand_, [this, function = std::move(function)]() mutable {
      27  queue_.push(std::move(function));
      28 
      29  if (!executing_) {
      30  executing_ = true;
      31  execute();
      32  }
      33  });
      34  }
      35 
      36  void next()
      37  {
      38  boost::asio::dispatch(strand_, [this] { execute(); });
      39  }
      40 
      41 private:
      42  void execute()
      43  {
      44  if (queue_.empty()) {
      45  executing_ = false;
      46  return;
      47  }
      48 
      49  queue_.front()();
      50  queue_.pop();
      51  }
      52 
      53  boost::asio::strand<Executor> strand_;
      54  std::queue<function_type> queue_;
      55  bool executing_{false};
      56 };
      57 
      58 } // internal
      59 } // packio
      60 
      61 #endif // PACKIO_MANUAL_STRAND_H
      The packio namespace.
      Definition: as.h:16
      +
      1 // This Source Code Form is subject to the terms of the Mozilla Public
      2 // License, v. 2.0. If a copy of the MPL was not distributed with this
      3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
      4 
      5 #ifndef PACKIO_MANUAL_STRAND_H
      6 #define PACKIO_MANUAL_STRAND_H
      7 
      8 #include <queue>
      9 #include <boost/asio.hpp>
      10 
      11 #include "unique_function.h"
      12 
      13 namespace packio {
      14 namespace internal {
      15 
      16 template <typename Executor>
      17 class manual_strand {
      18 public:
      19  using function_type = unique_function<void()>;
      20 
      21  manual_strand(const Executor& executor) : strand_{executor} {}
      22 
      23  void push(function_type function)
      24  {
      25  boost::asio::dispatch(
      26  strand_, [this, function = std::move(function)]() mutable {
      27  queue_.push(std::move(function));
      28 
      29  if (!executing_) {
      30  executing_ = true;
      31  execute();
      32  }
      33  });
      34  }
      35 
      36  void next()
      37  {
      38  boost::asio::dispatch(strand_, [this] { execute(); });
      39  }
      40 
      41 private:
      42  void execute()
      43  {
      44  if (queue_.empty()) {
      45  executing_ = false;
      46  return;
      47  }
      48 
      49  auto function = std::move(queue_.front());
      50  queue_.pop();
      51  function();
      52  }
      53 
      54  boost::asio::strand<Executor> strand_;
      55  std::queue<function_type> queue_;
      56  bool executing_{false};
      57 };
      58 
      59 } // internal
      60 } // packio
      61 
      62 #endif // PACKIO_MANUAL_STRAND_H
      The packio namespace.
      Definition: client.h:30
      diff --git a/docs/msgpack__rpc_8h_source.html b/docs/msgpack__rpc_8h_source.html index 9c3c165..d3358a5 100644 --- a/docs/msgpack__rpc_8h_source.html +++ b/docs/msgpack__rpc_8h_source.html @@ -68,7 +68,7 @@
      msgpack_rpc.h
      -Go to the documentation of this file.
      1 // This Source Code Form is subject to the terms of the Mozilla Public
      2 // License, v. 2.0. If a copy of the MPL was not distributed with this
      3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
      4 
      5 #ifndef PACKIO_MSGPACK_RPC_H
      6 #define PACKIO_MSGPACK_RPC_H
      7 
      10 
      11 #include <cstdint>
      12 
      13 namespace packio {
      14 
      16 using id_type = uint32_t;
      17 
      18 enum class msgpack_rpc_type { request = 0, response = 1, notification = 2 };
      19 
      20 } // packio
      21 
      22 #endif // PACKIO_MSGPACK_RPC_H
      The packio namespace.
      Definition: as.h:16
      +Go to the documentation of this file.
      1 // This Source Code Form is subject to the terms of the Mozilla Public
      2 // License, v. 2.0. If a copy of the MPL was not distributed with this
      3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
      4 
      5 #ifndef PACKIO_MSGPACK_RPC_H
      6 #define PACKIO_MSGPACK_RPC_H
      7 
      10 
      11 #include <cstdint>
      12 
      13 namespace packio {
      14 
      16 using id_type = uint32_t;
      17 
      18 enum class msgpack_rpc_type { request = 0, response = 1, notification = 2 };
      19 
      20 } // packio
      21 
      22 #endif // PACKIO_MSGPACK_RPC_H
      The packio namespace.
      Definition: client.h:30
      uint32_t id_type
      Type used to store call IDs.
      Definition: msgpack_rpc.h:16
      diff --git a/docs/namespacemembers.html b/docs/namespacemembers.html index d7f119d..69fefd0 100644 --- a/docs/namespacemembers.html +++ b/docs/namespacemembers.html @@ -61,9 +61,6 @@
      Here is a list of all documented namespace members with links to the namespaces they belong to:
      packio Namespace Reference
      @@ -112,17 +111,6 @@ }
 The error codes enumeration. More...
 
- - - - - - - - -

-Functions

template<typename Result , typename AsCallHandler >
auto as (AsCallHandler &&handler, std::enable_if_t<!std::is_void_v< Result >, void *>=nullptr)
 Function used to wrap a typed call handler. More...
 
template<typename Result , typename AsVoidCallHandler >
auto as (AsVoidCallHandler &&handler, std::enable_if_t< std::is_void_v< Result >, void *>=nullptr)
 Function used to wrap a call handler that expects a void result. More...
 

Detailed Description

The packio namespace.

@@ -163,85 +151,6 @@

Function Documentation

- -

◆ as() [1/2]

- -
-
-
-template<typename Result , typename AsCallHandler >
- - - - - - - - - - - - - - - - - - -
auto packio::as (AsCallHandler && handler,
std::enable_if_t<!std::is_void_v< Result >, void *>  = nullptr 
)
-
- -

Function used to wrap a typed call handler.

-

This function is used to provide a call handler that expects a specific return type. If the procedure call succeeds but the returned type is not what is expected, the handler will be called with error::bad_result_type. The optional given as second argument will have a value only if the error code is error::success.

-
Template Parameters
- - -
ResultThe expected return type for the procedure
-
-
-
Parameters
- - -
handlerCall handler to wrap. Must satisfy traits::AsCallHandler
-
-
- -
-
- -

◆ as() [2/2]

- -
-
-
-template<typename Result , typename AsVoidCallHandler >
- - - - - - - - - - - - - - - - - - -
auto packio::as (AsVoidCallHandler && handler,
std::enable_if_t< std::is_void_v< Result >, void *>  = nullptr 
)
-
- -

Function used to wrap a call handler that expects a void result.

-

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts. The only point of this wrapper is to verify that the remote procedure returned void. If the remote procedure returns any other type, this wrapper will set the error code to error::bad_result_type.

-
diff --git a/docs/packio_8h.html b/docs/packio_8h.html index 8f3ccc1..289cbe6 100644 --- a/docs/packio_8h.html +++ b/docs/packio_8h.html @@ -73,8 +73,7 @@

Main include file. More...

-
#include "as.h"
-#include "client.h"
+
#include "client.h"
#include "dispatcher.h"
#include "error_code.h"
#include "handler.h"
diff --git a/docs/packio_8h_source.html b/docs/packio_8h_source.html index 0f0b03b..6c2ecd3 100644 --- a/docs/packio_8h_source.html +++ b/docs/packio_8h_source.html @@ -68,8 +68,7 @@
packio.h
-Go to the documentation of this file.
1 #ifndef PACKIO_PACKIO_H
2 #define PACKIO_PACKIO_H
3 
10 
11 #include "as.h"
12 #include "client.h"
13 #include "dispatcher.h"
14 #include "error_code.h"
15 #include "handler.h"
16 #include "server.h"
17 
18 #endif // PACKIO_PACKIO_H
Function as.
-
Enum error.
+Go to the documentation of this file.
1 #ifndef PACKIO_PACKIO_H
2 #define PACKIO_PACKIO_H
3 
10 
11 #include "client.h"
12 #include "dispatcher.h"
13 #include "error_code.h"
14 #include "handler.h"
15 #include "server.h"
16 
17 #endif // PACKIO_PACKIO_H
Enum error.
Class server.
Class dispatcher.
Class client.
diff --git a/docs/search/all_0.js b/docs/search/all_0.js index e182c5a..ae05d76 100644 --- a/docs/search/all_0.js +++ b/docs/search/all_0.js @@ -4,12 +4,8 @@ var searchData= ['acceptor_5ftype',['acceptor_type',['../classpackio_1_1server.html#a16f4fbd6d3777b72786029baa2ff5e10',1,'packio::server']]], ['add',['add',['../classpackio_1_1dispatcher.html#aff6c4d31f265b4590f62122e9b2548a0',1,'packio::dispatcher']]], ['add_5fasync',['add_async',['../classpackio_1_1dispatcher.html#a5411e4849d5a68bc42cb5ca18dda1512',1,'packio::dispatcher']]], - ['as',['as',['../namespacepackio.html#accd75e308d49125935750356b345368c',1,'packio::as(AsCallHandler &&handler, std::enable_if_t<!std::is_void_v< Result >, void *>=nullptr)'],['../namespacepackio.html#a62c17c043ff8e7eddcd61fccfae4a6e1',1,'packio::as(AsVoidCallHandler &&handler, std::enable_if_t< std::is_void_v< Result >, void *>=nullptr)']]], - ['as_2eh',['as.h',['../as_8h.html',1,'']]], - ['ascallhandler',['AsCallHandler',['../structpackio_1_1traits_1_1AsCallHandler.html',1,'packio::traits']]], - ['asvoidcallhandler',['AsVoidCallHandler',['../structpackio_1_1traits_1_1AsVoidCallHandler.html',1,'packio::traits']]], - ['async_5fcall',['async_call',['../classpackio_1_1client.html#ad90463809693d4c237e3bad0e9b1de8f',1,'packio::client::async_call(std::string_view name, std::tuple< Args... > args, CallHandler &&handler)'],['../classpackio_1_1client.html#a0970b2e2261aa5878809b87cdfbab061',1,'packio::client::async_call(std::string_view name, CallHandler &&handler)']]], - ['async_5fnotify',['async_notify',['../classpackio_1_1client.html#a9b8d81b4dcb8bb003a28a195e360b293',1,'packio::client::async_notify(std::string_view name, std::tuple< Args... > args, NotifyHandler &&handler)'],['../classpackio_1_1client.html#a1bbdfd5fa426f0120c69cbcabe2bf356',1,'packio::client::async_notify(std::string_view name, NotifyHandler &&handler)']]], + ['async_5fcall',['async_call',['../classpackio_1_1client.html#a5d5a56eb59280c790e7bc2c3a709cb2c',1,'packio::client::async_call(std::string_view name, const std::tuple< Args... > &args, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)'],['../classpackio_1_1client.html#af53e5e26ead39ec87790dcc5a04040b9',1,'packio::client::async_call(std::string_view name, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)']]], + ['async_5fnotify',['async_notify',['../classpackio_1_1client.html#abd127ba2e3954fcc5804a8ff43b037fb',1,'packio::client::async_notify(std::string_view name, const std::tuple< Args... > &args, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())'],['../classpackio_1_1client.html#a291fd1093b65f9bd61f5d04151d46b17',1,'packio::client::async_notify(std::string_view name, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())']]], ['async_5fserve',['async_serve',['../classpackio_1_1server.html#a0062c078ac8b5bf0f8c695f878eaaf01',1,'packio::server']]], ['async_5fserve_5fforever',['async_serve_forever',['../classpackio_1_1server.html#a11649b360d7d9ed8d147974e3f20cdd5',1,'packio::server']]], ['asyncprocedure',['AsyncProcedure',['../structpackio_1_1traits_1_1AsyncProcedure.html',1,'packio::traits']]] diff --git a/docs/search/classes_0.js b/docs/search/classes_0.js index 49fe024..f713f4b 100644 --- a/docs/search/classes_0.js +++ b/docs/search/classes_0.js @@ -1,6 +1,4 @@ var searchData= [ - ['ascallhandler',['AsCallHandler',['../structpackio_1_1traits_1_1AsCallHandler.html',1,'packio::traits']]], - ['asvoidcallhandler',['AsVoidCallHandler',['../structpackio_1_1traits_1_1AsVoidCallHandler.html',1,'packio::traits']]], ['asyncprocedure',['AsyncProcedure',['../structpackio_1_1traits_1_1AsyncProcedure.html',1,'packio::traits']]] ]; diff --git a/docs/search/files_0.js b/docs/search/files_0.js index 918d527..8dd0f74 100644 --- a/docs/search/files_0.js +++ b/docs/search/files_0.js @@ -1,4 +1,4 @@ var searchData= [ - ['as_2eh',['as.h',['../as_8h.html',1,'']]] + ['client_2eh',['client.h',['../client_8h.html',1,'']]] ]; diff --git a/docs/search/files_1.js b/docs/search/files_1.js index 8dd0f74..a3f0e25 100644 --- a/docs/search/files_1.js +++ b/docs/search/files_1.js @@ -1,4 +1,4 @@ var searchData= [ - ['client_2eh',['client.h',['../client_8h.html',1,'']]] + ['dispatcher_2eh',['dispatcher.h',['../dispatcher_8h.html',1,'']]] ]; diff --git a/docs/search/files_2.js b/docs/search/files_2.js index a3f0e25..05f41ec 100644 --- a/docs/search/files_2.js +++ b/docs/search/files_2.js @@ -1,4 +1,4 @@ var searchData= [ - ['dispatcher_2eh',['dispatcher.h',['../dispatcher_8h.html',1,'']]] + ['error_5fcode_2eh',['error_code.h',['../error__code_8h.html',1,'']]] ]; diff --git a/docs/search/files_3.js b/docs/search/files_3.js index 05f41ec..c843e4a 100644 --- a/docs/search/files_3.js +++ b/docs/search/files_3.js @@ -1,4 +1,4 @@ var searchData= [ - ['error_5fcode_2eh',['error_code.h',['../error__code_8h.html',1,'']]] + ['handler_2eh',['handler.h',['../handler_8h.html',1,'']]] ]; diff --git a/docs/search/files_4.js b/docs/search/files_4.js index c843e4a..537bc66 100644 --- a/docs/search/files_4.js +++ b/docs/search/files_4.js @@ -1,4 +1,4 @@ var searchData= [ - ['handler_2eh',['handler.h',['../handler_8h.html',1,'']]] + ['msgpack_5frpc_2eh',['msgpack_rpc.h',['../msgpack__rpc_8h.html',1,'']]] ]; diff --git a/docs/search/files_5.js b/docs/search/files_5.js index 537bc66..1d1ac8e 100644 --- a/docs/search/files_5.js +++ b/docs/search/files_5.js @@ -1,4 +1,4 @@ var searchData= [ - ['msgpack_5frpc_2eh',['msgpack_rpc.h',['../msgpack__rpc_8h.html',1,'']]] + ['packio_2eh',['packio.h',['../packio_8h.html',1,'']]] ]; diff --git a/docs/search/files_6.js b/docs/search/files_6.js index 1d1ac8e..f156bbd 100644 --- a/docs/search/files_6.js +++ b/docs/search/files_6.js @@ -1,4 +1,5 @@ var searchData= [ - ['packio_2eh',['packio.h',['../packio_8h.html',1,'']]] + ['server_2eh',['server.h',['../server_8h.html',1,'']]], + ['server_5fsession_2eh',['server_session.h',['../server__session_8h.html',1,'']]] ]; diff --git a/docs/search/files_7.js b/docs/search/files_7.js index f156bbd..d51b737 100644 --- a/docs/search/files_7.js +++ b/docs/search/files_7.js @@ -1,5 +1,4 @@ var searchData= [ - ['server_2eh',['server.h',['../server_8h.html',1,'']]], - ['server_5fsession_2eh',['server_session.h',['../server__session_8h.html',1,'']]] + ['traits_2eh',['traits.h',['../traits_8h.html',1,'']]] ]; diff --git a/docs/search/functions_0.js b/docs/search/functions_0.js index e170f89..68e45c5 100644 --- a/docs/search/functions_0.js +++ b/docs/search/functions_0.js @@ -3,9 +3,8 @@ var searchData= ['acceptor',['acceptor',['../classpackio_1_1server.html#af148ab8376dcc68faa2e0904a84d2c47',1,'packio::server::acceptor()'],['../classpackio_1_1server.html#a99bca37b360590c4e901250cc8499c26',1,'packio::server::acceptor() const']]], ['add',['add',['../classpackio_1_1dispatcher.html#aff6c4d31f265b4590f62122e9b2548a0',1,'packio::dispatcher']]], ['add_5fasync',['add_async',['../classpackio_1_1dispatcher.html#a5411e4849d5a68bc42cb5ca18dda1512',1,'packio::dispatcher']]], - ['as',['as',['../namespacepackio.html#accd75e308d49125935750356b345368c',1,'packio::as(AsCallHandler &&handler, std::enable_if_t<!std::is_void_v< Result >, void *>=nullptr)'],['../namespacepackio.html#a62c17c043ff8e7eddcd61fccfae4a6e1',1,'packio::as(AsVoidCallHandler &&handler, std::enable_if_t< std::is_void_v< Result >, void *>=nullptr)']]], - ['async_5fcall',['async_call',['../classpackio_1_1client.html#ad90463809693d4c237e3bad0e9b1de8f',1,'packio::client::async_call(std::string_view name, std::tuple< Args... > args, CallHandler &&handler)'],['../classpackio_1_1client.html#a0970b2e2261aa5878809b87cdfbab061',1,'packio::client::async_call(std::string_view name, CallHandler &&handler)']]], - ['async_5fnotify',['async_notify',['../classpackio_1_1client.html#a9b8d81b4dcb8bb003a28a195e360b293',1,'packio::client::async_notify(std::string_view name, std::tuple< Args... > args, NotifyHandler &&handler)'],['../classpackio_1_1client.html#a1bbdfd5fa426f0120c69cbcabe2bf356',1,'packio::client::async_notify(std::string_view name, NotifyHandler &&handler)']]], + ['async_5fcall',['async_call',['../classpackio_1_1client.html#a5d5a56eb59280c790e7bc2c3a709cb2c',1,'packio::client::async_call(std::string_view name, const std::tuple< Args... > &args, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)'],['../classpackio_1_1client.html#af53e5e26ead39ec87790dcc5a04040b9',1,'packio::client::async_call(std::string_view name, CallHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type(), std::optional< std::reference_wrapper< id_type >> call_id=std::nullopt)']]], + ['async_5fnotify',['async_notify',['../classpackio_1_1client.html#abd127ba2e3954fcc5804a8ff43b037fb',1,'packio::client::async_notify(std::string_view name, const std::tuple< Args... > &args, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())'],['../classpackio_1_1client.html#a291fd1093b65f9bd61f5d04151d46b17',1,'packio::client::async_notify(std::string_view name, NotifyHandler &&handler=typename boost::asio::default_completion_token< executor_type >::type())']]], ['async_5fserve',['async_serve',['../classpackio_1_1server.html#a0062c078ac8b5bf0f8c695f878eaaf01',1,'packio::server']]], ['async_5fserve_5fforever',['async_serve_forever',['../classpackio_1_1server.html#a11649b360d7d9ed8d147974e3f20cdd5',1,'packio::server']]] ]; diff --git a/docs/search/searchdata.js b/docs/search/searchdata.js index d8cf99d..7eda49e 100644 --- a/docs/search/searchdata.js +++ b/docs/search/searchdata.js @@ -3,7 +3,7 @@ var indexSectionsWithContent = 0: "abcdefghikmnoprstu~", 1: "acdns", 2: "p", - 3: "acdehmpst", + 3: "cdehmpst", 4: "acdghkors~", 5: "k", 6: "adefimps", diff --git a/docs/server_8h_source.html b/docs/server_8h_source.html index 8007be6..bc49663 100644 --- a/docs/server_8h_source.html +++ b/docs/server_8h_source.html @@ -68,7 +68,7 @@
server.h
-Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_SERVER_H
6 #define PACKIO_SERVER_H
7 
10 
11 #include <memory>
12 #include <boost/asio.hpp>
13 #include <msgpack.hpp>
14 
15 #include "dispatcher.h"
16 #include "internal/log.h"
17 #include "internal/utils.h"
18 #include "server_session.h"
19 #include "traits.h"
20 
21 namespace packio {
22 
26 template <typename Protocol, typename Dispatcher = default_dispatcher>
27 class server : public std::enable_shared_from_this<server<Protocol, Dispatcher>> {
28 public:
29  using protocol_type = Protocol;
30  using dispatcher_type = Dispatcher;
31  using session_type =
33  using acceptor_type = typename Protocol::acceptor;
34  using socket_type = typename Protocol::socket;
35  using executor_type =
36  typename acceptor_type::executor_type;
37  using std::enable_shared_from_this<server<Protocol, Dispatcher>>::shared_from_this;
38 
43  server(acceptor_type acceptor, std::shared_ptr<dispatcher_type> dispatcher)
44  : acceptor_{std::move(acceptor)}, dispatcher_ptr_{std::move(dispatcher)}
45  {
46  }
47 
51  : server{std::move(acceptor), std::make_shared<dispatcher_type>()}
52  {
53  }
54 
56  acceptor_type& acceptor() { return acceptor_; }
58  const acceptor_type& acceptor() const { return acceptor_; }
59 
61  std::shared_ptr<dispatcher_type> dispatcher() { return dispatcher_ptr_; }
63  std::shared_ptr<const dispatcher_type> dispatcher() const
64  {
65  return dispatcher_ptr_;
66  }
67 
69  executor_type get_executor() { return acceptor().get_executor(); }
70 
76  template <typename ServeHandler>
77  void async_serve(ServeHandler&& handler)
78  {
79  PACKIO_STATIC_ASSERT_TTRAIT(ServeHandler, session_type);
80  PACKIO_TRACE("async_serve");
81  acceptor_.async_accept(
82  [this,
83  self = shared_from_this(),
84  handler = std::forward<ServeHandler>(handler)](
85  boost::system::error_code ec, socket_type sock) mutable {
86  std::shared_ptr<session_type> session;
87  if (ec) {
88  PACKIO_WARN("accept error: {}", ec.message());
89  }
90  else {
91  internal::set_no_delay(sock);
92  session = std::make_shared<session_type>(
93  std::move(sock), dispatcher_ptr_);
94  }
95  handler(ec, std::move(session));
96  });
97  }
98 
101  {
102  async_serve([this, self = shared_from_this()](auto ec, auto session) {
103  if (ec) {
104  return;
105  }
106 
107  session->start();
109  });
110  }
111 
112 private:
113  acceptor_type acceptor_;
114  std::shared_ptr<dispatcher_type> dispatcher_ptr_;
115 };
116 
117 } // packio
118 
119 #endif // PACKIO_SERVER_H
The server class.
Definition: server.h:27
+Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_SERVER_H
6 #define PACKIO_SERVER_H
7 
10 
11 #include <memory>
12 #include <boost/asio.hpp>
13 #include <msgpack.hpp>
14 
15 #include "dispatcher.h"
16 #include "internal/log.h"
17 #include "internal/utils.h"
18 #include "server_session.h"
19 #include "traits.h"
20 
21 namespace packio {
22 
26 template <typename Protocol, typename Dispatcher = default_dispatcher>
27 class server : public std::enable_shared_from_this<server<Protocol, Dispatcher>> {
28 public:
29  using protocol_type = Protocol;
30  using dispatcher_type = Dispatcher;
31  using session_type =
33  using acceptor_type = typename Protocol::acceptor;
34  using socket_type = typename Protocol::socket;
35  using executor_type =
36  typename acceptor_type::executor_type;
37  using std::enable_shared_from_this<server<Protocol, Dispatcher>>::shared_from_this;
38 
43  server(acceptor_type acceptor, std::shared_ptr<dispatcher_type> dispatcher)
44  : acceptor_{std::move(acceptor)}, dispatcher_ptr_{std::move(dispatcher)}
45  {
46  }
47 
51  : server{std::move(acceptor), std::make_shared<dispatcher_type>()}
52  {
53  }
54 
56  acceptor_type& acceptor() { return acceptor_; }
58  const acceptor_type& acceptor() const { return acceptor_; }
59 
61  std::shared_ptr<dispatcher_type> dispatcher() { return dispatcher_ptr_; }
63  std::shared_ptr<const dispatcher_type> dispatcher() const
64  {
65  return dispatcher_ptr_;
66  }
67 
69  executor_type get_executor() { return acceptor().get_executor(); }
70 
76  template <typename ServeHandler>
77  void async_serve(ServeHandler&& handler)
78  {
79  PACKIO_STATIC_ASSERT_TTRAIT(ServeHandler, session_type);
80  PACKIO_TRACE("async_serve");
81  acceptor_.async_accept(
82  [self = shared_from_this(),
83  handler = std::forward<ServeHandler>(handler)](
84  boost::system::error_code ec, socket_type sock) mutable {
85  std::shared_ptr<session_type> session;
86  if (ec) {
87  PACKIO_WARN("accept error: {}", ec.message());
88  }
89  else {
90  internal::set_no_delay(sock);
91  session = std::make_shared<session_type>(
92  std::move(sock), self->dispatcher_ptr_);
93  }
94  handler(ec, std::move(session));
95  });
96  }
97 
100  {
101  async_serve([self = shared_from_this()](auto ec, auto session) {
102  if (ec) {
103  return;
104  }
105 
106  session->start();
107  self->async_serve_forever();
108  });
109  }
110 
111 private:
112  acceptor_type acceptor_;
113  std::shared_ptr<dispatcher_type> dispatcher_ptr_;
114 };
115 
116 } // packio
117 
118 #endif // PACKIO_SERVER_H
The server class.
Definition: server.h:27
The server_session class, created by the server.
Definition: server_session.h:26
std::shared_ptr< dispatcher_type > dispatcher()
Get the dispatcher.
Definition: server.h:61
typename Protocol::acceptor acceptor_type
The acceptor type.
Definition: server.h:33
@@ -85,9 +85,9 @@
The dispatcher class, used to store and dispatch procedures.
Definition: dispatcher.h:32
const acceptor_type & acceptor() const
Get the underlying acceptor, const.
Definition: server.h:58
server(acceptor_type acceptor)
Simplified constructor: will allocate a new dispatcher automatically.
Definition: server.h:50
-
The packio namespace.
Definition: as.h:16
+
The packio namespace.
Definition: client.h:30
Class server_session.
-
void async_serve_forever()
Accept connections and automatically start the associated sessions forever.
Definition: server.h:100
+
void async_serve_forever()
Accept connections and automatically start the associated sessions forever.
Definition: server.h:99
server(acceptor_type acceptor, std::shared_ptr< dispatcher_type > dispatcher)
The constructor.
Definition: server.h:43
diff --git a/docs/server__session_8h_source.html b/docs/server__session_8h_source.html index a4a7e49..bc5b38e 100644 --- a/docs/server__session_8h_source.html +++ b/docs/server__session_8h_source.html @@ -68,7 +68,7 @@
server_session.h
-Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_SERVER_SESSION_H
6 #define PACKIO_SERVER_SESSION_H
7 
10 
11 #include <memory>
12 #include <queue>
13 #include <boost/asio.hpp>
14 #include <msgpack.hpp>
15 
16 #include "error_code.h"
17 #include "internal/log.h"
18 #include "internal/manual_strand.h"
19 #include "internal/msgpack_rpc.h"
20 #include "internal/utils.h"
21 
22 namespace packio {
23 
25 template <typename Protocol, typename Dispatcher>
27  : public std::enable_shared_from_this<server_session<Protocol, Dispatcher>> {
28 public:
29  using protocol_type = Protocol;
30  using socket_type = typename protocol_type::socket;
31  using std::enable_shared_from_this<server_session<Protocol, Dispatcher>>::shared_from_this;
32 
34  static constexpr size_t kDefaultBufferReserveSize = 4096;
35 
36  server_session(socket_type sock, std::shared_ptr<Dispatcher> dispatcher_ptr)
37  : socket_{std::move(sock)},
38  dispatcher_ptr_{std::move(dispatcher_ptr)},
39  wstrand_{socket_.get_executor()}
40  {
41  }
42 
44  socket_type& socket() { return socket_; }
46  const socket_type& socket() const { return socket_; }
47 
49  void set_buffer_reserve_size(std::size_t size) noexcept
50  {
51  buffer_reserve_size_ = size;
52  }
54  std::size_t get_buffer_reserve_size() const noexcept
55  {
56  return buffer_reserve_size_;
57  }
58 
60  void start() { async_read(std::make_unique<msgpack::unpacker>()); }
61 
62 private:
63  using buffer_type = msgpack::vrefbuffer; // non-owning buffer
64  using message_type = std::tuple<buffer_type, msgpack::object_handle>;
65  using message_queue = std::queue<std::unique_ptr<message_type>>;
66 
67  struct Call {
68  msgpack_rpc_type type;
69  id_type id;
70  std::string name;
71  msgpack::object args;
72  };
73 
74  void async_read(std::unique_ptr<msgpack::unpacker> unpacker)
75  {
76  // abort R/W on error
77  if (!socket_.is_open()) {
78  return;
79  }
80 
81  unpacker->reserve_buffer(buffer_reserve_size_);
82  auto buffer = boost::asio::buffer(
83  unpacker->buffer(), unpacker->buffer_capacity());
84  socket_.async_read_some(
85  buffer,
86  [this, self = shared_from_this(), unpacker = std::move(unpacker)](
87  boost::system::error_code ec, size_t length) mutable {
88  if (ec) {
89  PACKIO_WARN("read error: {}", ec.message());
90  close_connection();
91  return;
92  }
93 
94  PACKIO_TRACE("read: {}", length);
95  unpacker->buffer_consumed(length);
96 
97  for (msgpack::object_handle call; unpacker->next(call);) {
98  // handle the call asynchronously (post)
99  // to schedule the next read immediately
100  // this will allow parallel call handling
101  // in multi-threaded environments
102  async_dispatch(std::move(call));
103  }
104 
105  async_read(std::move(unpacker));
106  });
107  }
108 
109  void async_dispatch(msgpack::object_handle call)
110  {
111  boost::asio::post(
112  socket_.get_executor(),
113  [this, self = shared_from_this(), call = std::move(call)] {
114  dispatch(call.get());
115  });
116  }
117 
118  void dispatch(const msgpack::object& msgpack_call)
119  {
120  std::optional<Call> call = parse_call(msgpack_call);
121  if (!call) {
122  close_connection();
123  return;
124  }
125 
126  auto completion_handler =
127  [this, type = call->type, id = call->id, self = shared_from_this()](
128  boost::system::error_code ec, msgpack::object_handle result) {
129  if (type == msgpack_rpc_type::request) {
130  PACKIO_TRACE("result: {}", ec.message());
131  async_send_result(id, ec, std::move(result));
132  }
133  };
134 
135  const auto function = dispatcher_ptr_->get(call->name);
136  if (function) {
137  PACKIO_TRACE("call: {} (id={})", call->name, call->id);
138  (*function)(completion_handler, call->args);
139  }
140  else {
141  PACKIO_DEBUG("unknown function {}", call->name);
142  completion_handler(make_error_code(error::unknown_procedure), {});
143  }
144  }
145 
146  std::optional<Call> parse_call(const msgpack::object& call)
147  {
148  if (call.type != msgpack::type::ARRAY || call.via.array.size < 3) {
149  PACKIO_ERROR("unexpected message type: {}", call.type);
150  return std::nullopt;
151  }
152 
153  try {
154  int idx = 0;
155  id_type id = 0;
156  msgpack_rpc_type type = static_cast<msgpack_rpc_type>(
157  call.via.array.ptr[idx++].as<int>());
158 
159  std::size_t expected_size;
160  switch (type) {
161  case msgpack_rpc_type::request:
162  id = call.via.array.ptr[idx++].as<id_type>();
163  expected_size = 4;
164  break;
165  case msgpack_rpc_type::notification:
166  expected_size = 3;
167  break;
168  default:
169  PACKIO_ERROR("unexpected type: {}", type);
170  return std::nullopt;
171  }
172 
173  if (call.via.array.size != expected_size) {
174  PACKIO_ERROR("unexpected message size: {}", call.via.array.size);
175  return std::nullopt;
176  }
177 
178  std::string name = call.via.array.ptr[idx++].as<std::string>();
179  const msgpack::object& args = call.via.array.ptr[idx++];
180 
181  return Call{type, id, name, args};
182  }
183  catch (msgpack::type_error& exc) {
184  PACKIO_ERROR("unexpected message content: {}", exc.what());
185  (void)exc;
186  return std::nullopt;
187  }
188  }
189 
190  void async_send_result(
191  id_type id,
192  boost::system::error_code ec,
193  msgpack::object_handle result_handle)
194  {
195  // abort R/W on error
196  if (!socket_.is_open()) {
197  return;
198  }
199 
200  auto message_ptr = std::make_unique<message_type>();
201  msgpack::packer<buffer_type> packer(std::get<buffer_type>(*message_ptr));
202 
203  // serialize the result into the buffer
204  const auto pack = [&](auto&& error, auto&& result) {
205  packer.pack(std::forward_as_tuple(
206  static_cast<int>(msgpack_rpc_type::response),
207  id,
208  std::forward<decltype(error)>(error),
209  std::forward<decltype(result)>(result)));
210  };
211 
212  if (ec) {
213  if (result_handle.get().is_nil()) {
214  pack(ec.message(), msgpack::type::nil_t{});
215  }
216  else {
217  pack(result_handle.get(), msgpack::type::nil_t{});
218  }
219  }
220  else {
221  pack(msgpack::type::nil_t{}, result_handle.get());
222  }
223 
224  // move the result handle to the message pointer
225  // as the buffer is non-owning, we need to keep the result handle
226  // with the buffer
227  std::get<msgpack::object_handle>(*message_ptr) = std::move(result_handle);
228  async_send_message(std::move(message_ptr));
229  }
230 
231  void async_send_message(std::unique_ptr<message_type> message_ptr)
232  {
233  wstrand_.push([this,
234  self = shared_from_this(),
235  message_ptr = std::move(message_ptr)]() mutable {
236  using internal::buffer;
237 
238  auto buf = buffer(std::get<buffer_type>(*message_ptr));
239  boost::asio::async_write(
240  socket_,
241  buf,
242  [this,
243  self = std::move(self),
244  message_ptr = std::move(message_ptr)](
245  boost::system::error_code ec, size_t length) {
246  wstrand_.next();
247 
248  if (ec) {
249  PACKIO_WARN("write error: {}", ec.message());
250  close_connection();
251  return;
252  }
253 
254  PACKIO_TRACE("write: {}", length);
255  (void)length;
256  });
257  });
258  };
259 
260  void close_connection()
261  {
262  boost::system::error_code ec;
263  socket_.close(ec);
264  if (ec) {
265  PACKIO_WARN("close error: {}", ec.message());
266  }
267  }
268 
269  socket_type socket_;
270  std::size_t buffer_reserve_size_{kDefaultBufferReserveSize};
271  std::shared_ptr<Dispatcher> dispatcher_ptr_;
272  internal::manual_strand<typename socket_type::executor_type> wstrand_;
273 };
274 
275 } // packio
276 
277 #endif // PACKIO_SERVER_SESSION_H
msgpack-RPC related types and values
+Go to the documentation of this file.
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 #ifndef PACKIO_SERVER_SESSION_H
6 #define PACKIO_SERVER_SESSION_H
7 
10 
11 #include <memory>
12 #include <queue>
13 #include <boost/asio.hpp>
14 #include <msgpack.hpp>
15 
16 #include "error_code.h"
17 #include "internal/log.h"
18 #include "internal/manual_strand.h"
19 #include "internal/msgpack_rpc.h"
20 #include "internal/utils.h"
21 
22 namespace packio {
23 
25 template <typename Protocol, typename Dispatcher>
27  : public std::enable_shared_from_this<server_session<Protocol, Dispatcher>> {
28 public:
29  using protocol_type = Protocol;
30  using socket_type = typename protocol_type::socket;
31  using std::enable_shared_from_this<server_session<Protocol, Dispatcher>>::shared_from_this;
32 
34  static constexpr size_t kDefaultBufferReserveSize = 4096;
35 
36  server_session(socket_type sock, std::shared_ptr<Dispatcher> dispatcher_ptr)
37  : socket_{std::move(sock)},
38  dispatcher_ptr_{std::move(dispatcher_ptr)},
39  wstrand_{socket_.get_executor()}
40  {
41  }
42 
44  socket_type& socket() { return socket_; }
46  const socket_type& socket() const { return socket_; }
47 
49  void set_buffer_reserve_size(std::size_t size) noexcept
50  {
51  buffer_reserve_size_ = size;
52  }
54  std::size_t get_buffer_reserve_size() const noexcept
55  {
56  return buffer_reserve_size_;
57  }
58 
60  void start() { async_read(std::make_unique<msgpack::unpacker>()); }
61 
62 private:
63  using buffer_type = msgpack::vrefbuffer; // non-owning buffer
64  using message_type = std::tuple<buffer_type, msgpack::object_handle>;
65  using message_queue = std::queue<std::unique_ptr<message_type>>;
66 
67  struct Call {
68  msgpack_rpc_type type;
69  id_type id;
70  std::string name;
71  msgpack::object args;
72  };
73 
74  void async_read(std::unique_ptr<msgpack::unpacker> unpacker)
75  {
76  // abort R/W on error
77  if (!socket_.is_open()) {
78  return;
79  }
80 
81  unpacker->reserve_buffer(buffer_reserve_size_);
82  auto buffer = boost::asio::buffer(
83  unpacker->buffer(), unpacker->buffer_capacity());
84  socket_.async_read_some(
85  buffer,
86  [self = shared_from_this(), unpacker = std::move(unpacker)](
87  boost::system::error_code ec, size_t length) mutable {
88  if (ec) {
89  PACKIO_WARN("read error: {}", ec.message());
90  self->close_connection();
91  return;
92  }
93 
94  PACKIO_TRACE("read: {}", length);
95  unpacker->buffer_consumed(length);
96 
97  for (msgpack::object_handle call; unpacker->next(call);) {
98  // handle the call asynchronously (post)
99  // to schedule the next read immediately
100  // this will allow parallel call handling
101  // in multi-threaded environments
102  self->async_dispatch(std::move(call));
103  }
104 
105  self->async_read(std::move(unpacker));
106  });
107  }
108 
109  void async_dispatch(msgpack::object_handle call)
110  {
111  boost::asio::post(
112  socket_.get_executor(),
113  [self = shared_from_this(), call = std::move(call)] {
114  self->dispatch(call.get());
115  });
116  }
117 
118  void dispatch(const msgpack::object& msgpack_call)
119  {
120  std::optional<Call> call = parse_call(msgpack_call);
121  if (!call) {
122  close_connection();
123  return;
124  }
125 
126  auto completion_handler =
127  [type = call->type, id = call->id, self = shared_from_this()](
128  boost::system::error_code ec, msgpack::object_handle result) {
129  if (type == msgpack_rpc_type::request) {
130  PACKIO_TRACE("result: {}", ec.message());
131  self->async_send_result(id, ec, std::move(result));
132  }
133  };
134 
135  const auto function = dispatcher_ptr_->get(call->name);
136  if (function) {
137  PACKIO_TRACE("call: {} (id={})", call->name, call->id);
138  (*function)(completion_handler, call->args);
139  }
140  else {
141  PACKIO_DEBUG("unknown function {}", call->name);
142  completion_handler(make_error_code(error::unknown_procedure), {});
143  }
144  }
145 
146  std::optional<Call> parse_call(const msgpack::object& call)
147  {
148  if (call.type != msgpack::type::ARRAY || call.via.array.size < 3) {
149  PACKIO_ERROR("unexpected message type: {}", call.type);
150  return std::nullopt;
151  }
152 
153  try {
154  int idx = 0;
155  id_type id = 0;
156  msgpack_rpc_type type = static_cast<msgpack_rpc_type>(
157  call.via.array.ptr[idx++].as<int>());
158 
159  std::size_t expected_size;
160  switch (type) {
161  case msgpack_rpc_type::request:
162  id = call.via.array.ptr[idx++].as<id_type>();
163  expected_size = 4;
164  break;
165  case msgpack_rpc_type::notification:
166  expected_size = 3;
167  break;
168  default:
169  PACKIO_ERROR("unexpected type: {}", type);
170  return std::nullopt;
171  }
172 
173  if (call.via.array.size != expected_size) {
174  PACKIO_ERROR("unexpected message size: {}", call.via.array.size);
175  return std::nullopt;
176  }
177 
178  std::string name = call.via.array.ptr[idx++].as<std::string>();
179  const msgpack::object& args = call.via.array.ptr[idx++];
180 
181  return Call{type, id, name, args};
182  }
183  catch (msgpack::type_error& exc) {
184  PACKIO_ERROR("unexpected message content: {}", exc.what());
185  (void)exc;
186  return std::nullopt;
187  }
188  }
189 
190  void async_send_result(
191  id_type id,
192  boost::system::error_code ec,
193  msgpack::object_handle result_handle)
194  {
195  // abort R/W on error
196  if (!socket_.is_open()) {
197  return;
198  }
199 
200  auto message_ptr = std::make_unique<message_type>();
201  msgpack::packer<buffer_type> packer(std::get<buffer_type>(*message_ptr));
202 
203  // serialize the result into the buffer
204  const auto pack = [&](auto&& error, auto&& result) {
205  packer.pack(std::forward_as_tuple(
206  static_cast<int>(msgpack_rpc_type::response),
207  id,
208  std::forward<decltype(error)>(error),
209  std::forward<decltype(result)>(result)));
210  };
211 
212  if (ec) {
213  if (result_handle.get().is_nil()) {
214  pack(ec.message(), msgpack::type::nil_t{});
215  }
216  else {
217  pack(result_handle.get(), msgpack::type::nil_t{});
218  }
219  }
220  else {
221  pack(msgpack::type::nil_t{}, result_handle.get());
222  }
223 
224  // move the result handle to the message pointer
225  // as the buffer is non-owning, we need to keep the result handle
226  // with the buffer
227  std::get<msgpack::object_handle>(*message_ptr) = std::move(result_handle);
228  async_send_message(std::move(message_ptr));
229  }
230 
231  void async_send_message(std::unique_ptr<message_type> message_ptr)
232  {
233  wstrand_.push([this,
234  self = shared_from_this(),
235  message_ptr = std::move(message_ptr)]() mutable {
236  using internal::buffer;
237 
238  auto buf = buffer(std::get<buffer_type>(*message_ptr));
239  boost::asio::async_write(
240  socket_,
241  buf,
242  [self = std::move(self), message_ptr = std::move(message_ptr)](
243  boost::system::error_code ec, size_t length) {
244  self->wstrand_.next();
245 
246  if (ec) {
247  PACKIO_WARN("write error: {}", ec.message());
248  self->close_connection();
249  return;
250  }
251 
252  PACKIO_TRACE("write: {}", length);
253  (void)length;
254  });
255  });
256  };
257 
258  void close_connection()
259  {
260  boost::system::error_code ec;
261  socket_.close(ec);
262  if (ec) {
263  PACKIO_WARN("close error: {}", ec.message());
264  }
265  }
266 
267  socket_type socket_;
268  std::size_t buffer_reserve_size_{kDefaultBufferReserveSize};
269  std::shared_ptr<Dispatcher> dispatcher_ptr_;
270  internal::manual_strand<typename socket_type::executor_type> wstrand_;
271 };
272 
273 } // packio
274 
275 #endif // PACKIO_SERVER_SESSION_H
msgpack-RPC related types and values
The server_session class, created by the server.
Definition: server_session.h:26
Protocol protocol_type
The protocol type.
Definition: server_session.h:29
void set_buffer_reserve_size(std::size_t size) noexcept
Set the size reserved by the reception buffer.
Definition: server_session.h:49
@@ -82,7 +82,7 @@
error
The error codes enumeration.
Definition: error_code.h:17
typename protocol_type::socket socket_type
The socket type.
Definition: server_session.h:30
socket_type & socket()
Get the underlying socket.
Definition: server_session.h:44
-
The packio namespace.
Definition: as.h:16
+
The packio namespace.
Definition: client.h:30
uint32_t id_type
Type used to store call IDs.
Definition: msgpack_rpc.h:16
diff --git a/docs/structpackio_1_1traits_1_1CallHandler.html b/docs/structpackio_1_1traits_1_1CallHandler.html index 8a3114f..672c2aa 100644 --- a/docs/structpackio_1_1traits_1_1CallHandler.html +++ b/docs/structpackio_1_1traits_1_1CallHandler.html @@ -78,8 +78,10 @@ struct packio::traits::CallHandler< T >

CallHandler trait.

-

Handler used by client::async_call

    -
  • Must be callable with boost::system::error_code, msgpack::object_handle
  • +

    Handler used by client::async_call, must meet one of:

      +
    • Must be callable with boost::system::error_code, msgpack::object_handle
    • +
    • Must be callable with boost::system::error_code
    • +
    • Must be callable with boost::system::error_code, std::optional<T>

The documentation for this struct was generated from the following file:
  • packio/traits.h
  • diff --git a/docs/structpackio_1_1traits_1_1NotifyHandler.html b/docs/structpackio_1_1traits_1_1NotifyHandler.html index afde8c5..17a9604 100644 --- a/docs/structpackio_1_1traits_1_1NotifyHandler.html +++ b/docs/structpackio_1_1traits_1_1NotifyHandler.html @@ -78,7 +78,7 @@ struct packio::traits::NotifyHandler< T >

    NotifyHandler trait.

    -

    Handler used by client::async_notify

      +

      Handler used by client::async_notify

      • Must be callable with a boost::system::error_code

      The documentation for this struct was generated from the following file:
        diff --git a/docs/traits_8h.html b/docs/traits_8h.html index b70f0de..d10850d 100644 --- a/docs/traits_8h.html +++ b/docs/traits_8h.html @@ -90,12 +90,6 @@
struct  packio::traits::CallHandler< T >
 CallHandler trait. More...
 
struct  packio::traits::AsCallHandler< T, Result >
 AsCallHandler. More...
 
struct  packio::traits::AsVoidCallHandler< T >
 AsVoidCallHandler. More...
 
struct  packio::traits::ServeHandler< T, Session >
 ServeHandler trait. More...