xref: /aosp_15_r20/external/pigweed/pw_rpc/public/pw_rpc/internal/synchronous_call_impl.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 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_rpc/internal/config.h"
19 #include "pw_rpc/internal/method_info.h"
20 #include "pw_rpc/synchronous_call_result.h"
21 #include "pw_sync/timed_thread_notification.h"
22 
23 #if PW_RPC_DYNAMIC_ALLOCATION
24 #include PW_RPC_MAKE_UNIQUE_PTR_INCLUDE
25 #endif  // PW_RPC_DYNAMIC_ALLOCATION
26 
27 namespace pw::rpc::internal {
28 
29 template <typename Response>
30 struct SynchronousCallState {
OnCompletedCallbackSynchronousCallState31   auto OnCompletedCallback() {
32     return [this](const Response& response, Status status) {
33       result = SynchronousCallResult<Response>(status, response);
34       notify.release();
35     };
36   }
37 
OnRpcErrorCallbackSynchronousCallState38   auto OnRpcErrorCallback() {
39     return [this](Status status) {
40       result = SynchronousCallResult<Response>::RpcError(status);
41       notify.release();
42     };
43   }
44 
45   SynchronousCallResult<Response> result;
46   sync::TimedThreadNotification notify;
47 };
48 
49 class RawSynchronousCallState {
50  public:
RawSynchronousCallState(Function<void (ConstByteSpan,Status)> on_completed)51   RawSynchronousCallState(Function<void(ConstByteSpan, Status)> on_completed)
52       : on_completed_(std::move(on_completed)) {}
53 
OnCompletedCallback()54   auto OnCompletedCallback() {
55     return [this](ConstByteSpan response, Status status) {
56       if (on_completed_) {
57         on_completed_(response, status);
58       }
59       notify.release();
60     };
61   }
62 
OnRpcErrorCallback()63   auto OnRpcErrorCallback() {
64     return [this](Status status) {
65       error = status;
66       notify.release();
67     };
68   }
69 
70   Status error;
71   sync::TimedThreadNotification notify;
72 
73  private:
74   Function<void(ConstByteSpan, Status)> on_completed_;
75 };
76 
77 // Overloaded function to choose detween timeout and deadline APIs.
AcquireNotification(sync::TimedThreadNotification & notification,chrono::SystemClock::duration timeout)78 inline bool AcquireNotification(sync::TimedThreadNotification& notification,
79                                 chrono::SystemClock::duration timeout) {
80   return notification.try_acquire_for(timeout);
81 }
82 
AcquireNotification(sync::TimedThreadNotification & notification,chrono::SystemClock::time_point timeout)83 inline bool AcquireNotification(sync::TimedThreadNotification& notification,
84                                 chrono::SystemClock::time_point timeout) {
85   return notification.try_acquire_until(timeout);
86 }
87 
88 template <auto kRpcMethod,
89           typename Response = typename MethodInfo<kRpcMethod>::Response,
90           typename DoCall,
91           typename... TimeoutArg>
StructSynchronousCall(DoCall && do_call,TimeoutArg...timeout_arg)92 SynchronousCallResult<Response> StructSynchronousCall(
93     DoCall&& do_call, TimeoutArg... timeout_arg) {
94   static_assert(MethodInfo<kRpcMethod>::kType == MethodType::kUnary,
95                 "Only unary methods can be used with synchronous calls");
96 
97   // If dynamic allocation is enabled, heap-allocate the call_state.
98 #if PW_RPC_DYNAMIC_ALLOCATION
99   auto call_state_ptr = PW_RPC_MAKE_UNIQUE_PTR(SynchronousCallState<Response>);
100   SynchronousCallState<Response>& call_state(*call_state_ptr);
101 #else
102   SynchronousCallState<Response> call_state;
103 #endif  // PW_RPC_DYNAMIC_ALLOCATION
104 
105   auto call = std::forward<DoCall>(do_call)(call_state);
106 
107   // Wait for the notification based on the type of the timeout argument.
108   if constexpr (sizeof...(TimeoutArg) == 0) {
109     call_state.notify.acquire();  // Wait forever, since no timeout was given.
110   } else if (!AcquireNotification(call_state.notify, timeout_arg...)) {
111     return SynchronousCallResult<Response>::Timeout();
112   }
113 
114   return std::move(call_state.result);
115 }
116 
117 // Template for a raw synchronous call. Used for SynchronousCall,
118 // SynchronousCallFor, and SynchronousCallUntil. The type of the timeout
119 // argument is used to determine the behavior.
120 template <auto kRpcMethod, typename DoCall, typename... TimeoutArg>
RawSynchronousCall(Function<void (ConstByteSpan,Status)> && on_completed,DoCall && do_call,TimeoutArg...timeout_arg)121 Status RawSynchronousCall(Function<void(ConstByteSpan, Status)>&& on_completed,
122                           DoCall&& do_call,
123                           TimeoutArg... timeout_arg) {
124   static_assert(MethodInfo<kRpcMethod>::kType == MethodType::kUnary,
125                 "Only unary methods can be used with synchronous calls");
126 
127   RawSynchronousCallState call_state{std::move(on_completed)};
128 
129   auto call = std::forward<DoCall>(do_call)(call_state);
130 
131   // Wait for the notification based on the type of the timeout argument.
132   if constexpr (sizeof...(TimeoutArg) == 0) {
133     call_state.notify.acquire();  // Wait forever, since no timeout was given.
134   } else if (!AcquireNotification(call_state.notify, timeout_arg...)) {
135     return Status::DeadlineExceeded();
136   }
137 
138   return call_state.error;
139 }
140 
141 // Choose which call state object to use (raw or struct).
142 template <auto kRpcMethod,
143           typename Response =
144               typename internal::MethodInfo<kRpcMethod>::Response>
145 using CallState = std::conditional_t<
146     std::is_same_v<typename MethodInfo<kRpcMethod>::Request, void>,
147     RawSynchronousCallState,
148     SynchronousCallState<Response>>;
149 
150 // Invokes the RPC method free function using a call_state.
151 template <auto kRpcMethod, typename Request>
CallFreeFunction(Client & client,uint32_t channel_id,const Request & request)152 constexpr auto CallFreeFunction(Client& client,
153                                 uint32_t channel_id,
154                                 const Request& request) {
155   return [&client, channel_id, &request](CallState<kRpcMethod>& call_state) {
156     return kRpcMethod(client,
157                       channel_id,
158                       request,
159                       call_state.OnCompletedCallback(),
160                       call_state.OnRpcErrorCallback());
161   };
162 }
163 
164 // Invokes the RPC method free function using a call_state and a custom
165 // response.
166 template <
167     auto kRpcMethod,
168     typename Response = typename internal::MethodInfo<kRpcMethod>::Response,
169     typename Request>
CallFreeFunctionWithCustomResponse(Client & client,uint32_t channel_id,const Request & request)170 constexpr auto CallFreeFunctionWithCustomResponse(Client& client,
171                                                   uint32_t channel_id,
172                                                   const Request& request) {
173   return [&client, channel_id, &request](
174              CallState<kRpcMethod, Response>& call_state) {
175     constexpr auto kMemberFunction =
176         MethodInfo<kRpcMethod>::template FunctionTemplate<
177             typename MethodInfo<kRpcMethod>::ServiceClass,
178             Response>();
179     return (*kMemberFunction)(client,
180                               channel_id,
181                               request,
182                               call_state.OnCompletedCallback(),
183                               call_state.OnRpcErrorCallback());
184   };
185 }
186 
187 // Invokes the RPC function on the generated service client using a call_state.
188 template <auto kRpcMethod, typename GeneratedClient, typename Request>
CallGeneratedClient(const GeneratedClient & client,const Request & request)189 constexpr auto CallGeneratedClient(const GeneratedClient& client,
190                                    const Request& request) {
191   return [&client, &request](CallState<kRpcMethod>& call_state) {
192     constexpr auto kMemberFunction =
193         MethodInfo<kRpcMethod>::template Function<GeneratedClient>();
194     return (client.*kMemberFunction)(request,
195                                      call_state.OnCompletedCallback(),
196                                      call_state.OnRpcErrorCallback());
197   };
198 }
199 
200 }  // namespace pw::rpc::internal
201