• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <utility>
17 
18 #include "pw_chrono/system_clock.h"
19 #include "pw_rpc/client.h"
20 #include "pw_rpc/internal/method_info.h"
21 #include "pw_rpc/synchronous_call_result.h"
22 #include "pw_sync/timed_thread_notification.h"
23 
24 // Synchronous Call wrappers
25 //
26 // Wraps an asynchronous RPC client call, converting it to a synchronous
27 // interface.
28 //
29 // WARNING! This should not be called from any context that cannot be blocked!
30 // This method will block the calling thread until the RPC completes, and
31 // translate the response into a pw::rpc::SynchronousCallResult that contains
32 // the error type and status or the proto response.
33 //
34 // Example:
35 //
36 //   pw_rpc_EchoMessage request{.msg = "hello" };
37 //   pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
38 //     pw::rpc::SynchronousCall<EchoService::Echo>(rpc_client,
39 //                                                 channel_id,
40 //                                                 request);
41 //   if (result.ok()) {
42 //     printf("%s", result.response().msg);
43 //   }
44 //
45 // Note: The above example will block indefinitely. If you'd like to include a
46 // timeout for how long the call should block for, use the
47 // `SynchronousCallFor()` or `SynchronousCallUntil()` variants.
48 //
49 // Additionally, the use of a generated Client object is supported:
50 //
51 //   pw_rpc::nanopb::EchoService::client client;
52 //   pw_rpc_EchoMessage request{.msg = "hello" };
53 //   pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
54 //     pw::rpc::SynchronousCall<EchoService::Echo>(client, request);
55 //
56 //   if (result.ok()) {
57 //     printf("%s", result.response().msg);
58 //   }
59 
60 namespace pw::rpc {
61 namespace internal {
62 
63 template <typename Response>
64 struct SynchronousCallState {
OnCompletedCallbackSynchronousCallState65   auto OnCompletedCallback() {
66     return [this](const Response& response, Status status) {
67       result = SynchronousCallResult<Response>(status, response);
68       notify.release();
69     };
70   }
71 
OnRpcErrorCallbackSynchronousCallState72   auto OnRpcErrorCallback() {
73     return [this](Status status) {
74       result = SynchronousCallResult<Response>::RpcError(status);
75       notify.release();
76     };
77   }
78 
79   SynchronousCallResult<Response> result;
80   sync::TimedThreadNotification notify;
81 };
82 
83 }  // namespace internal
84 
85 // SynchronousCall
86 //
87 // Template arguments:
88 //   kRpcMethod: The RPC Method to invoke
89 //
90 // Arguments:
91 //   client: The pw::rpc::Client to use for the call
92 //   channel_id: The ID of the RPC channel to make the call on
93 //   request: The proto struct to send as the request
94 template <auto kRpcMethod>
95 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request)96 SynchronousCall(
97     Client& client,
98     uint32_t channel_id,
99     const typename internal::MethodInfo<kRpcMethod>::Request& request) {
100   using Info = internal::MethodInfo<kRpcMethod>;
101   using Response = typename Info::Response;
102   static_assert(Info::kType == MethodType::kUnary,
103                 "Only unary methods can be used with synchronous calls");
104 
105   internal::SynchronousCallState<Response> call_state;
106 
107   auto call = kRpcMethod(client,
108                          channel_id,
109                          request,
110                          call_state.OnCompletedCallback(),
111                          call_state.OnRpcErrorCallback());
112 
113   call_state.notify.acquire();
114 
115   return std::move(call_state.result);
116 }
117 
118 // SynchronousCall
119 //
120 // Template arguments:
121 //   kRpcMethod: The RPC Method to invoke
122 //
123 // Arguments:
124 //   client: The service Client to use for the call
125 //   request: The proto struct to send as the request
126 template <auto kRpcMethod>
127 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request)128 SynchronousCall(
129     const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
130     const typename internal::MethodInfo<kRpcMethod>::Request& request) {
131   using Info = internal::MethodInfo<kRpcMethod>;
132   using Response = typename Info::Response;
133   static_assert(Info::kType == MethodType::kUnary,
134                 "Only unary methods can be used with synchronous calls");
135 
136   constexpr auto Function =
137       Info::template Function<typename Info::GeneratedClient>();
138 
139   internal::SynchronousCallState<Response> call_state;
140 
141   auto call = (client.*Function)(request,
142                                  call_state.OnCompletedCallback(),
143                                  call_state.OnRpcErrorCallback());
144 
145   call_state.notify.acquire();
146 
147   return std::move(call_state.result);
148 }
149 
150 // SynchronousCallFor
151 //
152 // Template arguments:
153 //   kRpcMethod: The RPC Method to invoke
154 //
155 // Arguments:
156 //   client: The pw::rpc::Client to use for the call
157 //   channel_id: The ID of the RPC channel to make the call on
158 //   request: The proto struct to send as the request
159 //   timeout: Duration to block for before returning with Timeout
160 template <auto kRpcMethod>
161 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::duration timeout)162 SynchronousCallFor(
163     Client& client,
164     uint32_t channel_id,
165     const typename internal::MethodInfo<kRpcMethod>::Request& request,
166     chrono::SystemClock::duration timeout) {
167   using Info = internal::MethodInfo<kRpcMethod>;
168   using Response = typename Info::Response;
169   static_assert(Info::kType == MethodType::kUnary,
170                 "Only unary methods can be used with synchronous calls");
171 
172   internal::SynchronousCallState<Response> call_state;
173 
174   auto call = kRpcMethod(client,
175                          channel_id,
176                          request,
177                          call_state.OnCompletedCallback(),
178                          call_state.OnRpcErrorCallback());
179 
180   if (!call_state.notify.try_acquire_for(timeout)) {
181     return SynchronousCallResult<Response>::Timeout();
182   }
183 
184   return std::move(call_state.result);
185 }
186 
187 // SynchronousCallFor
188 //
189 // Template arguments:
190 //   kRpcMethod: The RPC Method to invoke
191 //
192 // Arguments:
193 //   client: The service Client to use for the call
194 //   request: The proto struct to send as the request
195 //   timeout: Duration to block for before returning with Timeout
196 template <auto kRpcMethod>
197 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::duration timeout)198 SynchronousCallFor(
199     const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
200     const typename internal::MethodInfo<kRpcMethod>::Request& request,
201     chrono::SystemClock::duration timeout) {
202   using Info = internal::MethodInfo<kRpcMethod>;
203   using Response = typename Info::Response;
204   static_assert(Info::kType == MethodType::kUnary,
205                 "Only unary methods can be used with synchronous calls");
206 
207   constexpr auto Function =
208       Info::template Function<typename Info::GeneratedClient>();
209 
210   internal::SynchronousCallState<Response> call_state;
211 
212   auto call = (client.*Function)(request,
213                                  call_state.OnCompletedCallback(),
214                                  call_state.OnRpcErrorCallback());
215 
216   if (!call_state.notify.try_acquire_for(timeout)) {
217     return SynchronousCallResult<Response>::Timeout();
218   }
219 
220   return std::move(call_state.result);
221 }
222 
223 // SynchronousCallUntil
224 //
225 // Template arguments:
226 //   kRpcMethod: The RPC Method to invoke
227 //
228 // Arguments:
229 //   client: The pw::rpc::Client to use for the call
230 //   channel_id: The ID of the RPC channel to make the call on
231 //   request: The proto struct to send as the request
232 //   deadline: Timepoint to block until before returning with Timeout
233 template <auto kRpcMethod>
234 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallUntil(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::time_point deadline)235 SynchronousCallUntil(
236     Client& client,
237     uint32_t channel_id,
238     const typename internal::MethodInfo<kRpcMethod>::Request& request,
239     chrono::SystemClock::time_point deadline) {
240   using Info = internal::MethodInfo<kRpcMethod>;
241   using Response = typename Info::Response;
242   static_assert(Info::kType == MethodType::kUnary,
243                 "Only unary methods can be used with synchronous calls");
244 
245   internal::SynchronousCallState<Response> call_state;
246 
247   auto call = kRpcMethod(client,
248                          channel_id,
249                          request,
250                          call_state.OnCompletedCallback(),
251                          call_state.OnRpcErrorCallback());
252 
253   if (!call_state.notify.try_acquire_until(deadline)) {
254     return SynchronousCallResult<Response>::Timeout();
255   }
256 
257   return std::move(call_state.result);
258 }
259 
260 // SynchronousCallUntil
261 //
262 // Template arguments:
263 //   kRpcMethod: The RPC Method to invoke
264 //
265 // Arguments:
266 //   client: The service Client to use for the call
267 //   request: The proto struct to send as the request
268 //   deadline: Timepoint to block until before returning with Timeout
269 template <auto kRpcMethod>
270 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallUntil(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::time_point deadline)271 SynchronousCallUntil(
272     const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
273     const typename internal::MethodInfo<kRpcMethod>::Request& request,
274     chrono::SystemClock::time_point deadline) {
275   using Info = internal::MethodInfo<kRpcMethod>;
276   using Response = typename Info::Response;
277   static_assert(Info::kType == MethodType::kUnary,
278                 "Only unary methods can be used with synchronous calls");
279 
280   constexpr auto Function =
281       Info::template Function<typename Info::GeneratedClient>();
282 
283   internal::SynchronousCallState<Response> call_state;
284 
285   auto call = (client.*Function)(request,
286                                  call_state.OnCompletedCallback(),
287                                  call_state.OnRpcErrorCallback());
288 
289   if (!call_state.notify.try_acquire_until(deadline)) {
290     return SynchronousCallResult<Response>::Timeout();
291   }
292 
293   return std::move(call_state.result);
294 }
295 }  // namespace pw::rpc
296