xref: /aosp_15_r20/external/grpc-grpc/test/cpp/end2end/connection_attempt_injector.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1 // Copyright 2016 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "test/cpp/end2end/connection_attempt_injector.h"
16 
17 #include <memory>
18 
19 #include "absl/memory/memory.h"
20 #include "absl/utility/utility.h"
21 
22 #include "src/core/lib/address_utils/sockaddr_utils.h"
23 #include "src/core/lib/event_engine/default_event_engine.h"
24 #include "src/core/lib/gprpp/sync.h"
25 #include "src/core/lib/iomgr/exec_ctx.h"
26 
27 // defined in tcp_client.cc
28 extern grpc_tcp_client_vtable* grpc_tcp_client_impl;
29 
30 using ::grpc_event_engine::experimental::EndpointConfig;
31 
32 namespace grpc {
33 namespace testing {
34 
35 //
36 // ConnectionAttemptInjector static setup
37 //
38 
39 namespace {
40 
41 grpc_tcp_client_vtable* g_original_vtable = nullptr;
42 
43 grpc_core::Mutex* g_mu = nullptr;
44 ConnectionAttemptInjector* g_injector ABSL_GUARDED_BY(*g_mu) = nullptr;
45 
46 }  // namespace
47 
48 grpc_tcp_client_vtable ConnectionAttemptInjector::kDelayedConnectVTable = {
49     ConnectionAttemptInjector::TcpConnect,
50     ConnectionAttemptInjector::TcpConnectCancel};
51 
Init()52 void ConnectionAttemptInjector::Init() {
53   g_mu = new grpc_core::Mutex();
54   g_original_vtable = grpc_tcp_client_impl;
55   grpc_tcp_client_impl = &kDelayedConnectVTable;
56 }
57 
TcpConnect(grpc_closure * closure,grpc_endpoint ** ep,grpc_pollset_set * interested_parties,const EndpointConfig & config,const grpc_resolved_address * addr,grpc_core::Timestamp deadline)58 int64_t ConnectionAttemptInjector::TcpConnect(
59     grpc_closure* closure, grpc_endpoint** ep,
60     grpc_pollset_set* interested_parties, const EndpointConfig& config,
61     const grpc_resolved_address* addr, grpc_core::Timestamp deadline) {
62   grpc_core::MutexLock lock(g_mu);
63   // If there's no injector, use the original vtable.
64   if (g_injector == nullptr) {
65     g_original_vtable->connect(closure, ep, interested_parties, config, addr,
66                                deadline);
67     return 0;
68   }
69   // Otherwise, use the injector.
70   g_injector->HandleConnection(closure, ep, interested_parties, config, addr,
71                                deadline);
72   return 0;
73 }
74 
75 // TODO(vigneshbabu): This method should check whether the connect attempt has
76 // actually been started, and if so, it should call
77 // g_original_vtable->cancel_connect(). If the attempt has not actually been
78 // started, it should mark the connect request as cancelled, so that when the
79 // request is resumed, it will not actually proceed.
TcpConnectCancel(int64_t)80 bool ConnectionAttemptInjector::TcpConnectCancel(
81     int64_t /*connection_handle*/) {
82   return false;
83 }
84 
85 //
86 // ConnectionAttemptInjector instance
87 //
88 
ConnectionAttemptInjector()89 ConnectionAttemptInjector::ConnectionAttemptInjector() {
90   // Fail if ConnectionAttemptInjector::Init() was not called after
91   // grpc_init() to inject the vtable.
92   GPR_ASSERT(grpc_tcp_client_impl == &kDelayedConnectVTable);
93   grpc_core::MutexLock lock(g_mu);
94   GPR_ASSERT(g_injector == nullptr);
95   g_injector = this;
96 }
97 
~ConnectionAttemptInjector()98 ConnectionAttemptInjector::~ConnectionAttemptInjector() {
99   grpc_core::MutexLock lock(g_mu);
100   g_injector = nullptr;
101 }
102 
103 std::unique_ptr<ConnectionAttemptInjector::Hold>
AddHold(int port,bool intercept_completion)104 ConnectionAttemptInjector::AddHold(int port, bool intercept_completion) {
105   grpc_core::MutexLock lock(&mu_);
106   auto hold = std::make_unique<Hold>(this, port, intercept_completion);
107   holds_.push_back(hold.get());
108   return hold;
109 }
110 
SetDelay(grpc_core::Duration delay)111 void ConnectionAttemptInjector::SetDelay(grpc_core::Duration delay) {
112   grpc_core::MutexLock lock(&mu_);
113   delay_ = delay;
114 }
115 
HandleConnection(grpc_closure * closure,grpc_endpoint ** ep,grpc_pollset_set * interested_parties,const EndpointConfig & config,const grpc_resolved_address * addr,grpc_core::Timestamp deadline)116 void ConnectionAttemptInjector::HandleConnection(
117     grpc_closure* closure, grpc_endpoint** ep,
118     grpc_pollset_set* interested_parties, const EndpointConfig& config,
119     const grpc_resolved_address* addr, grpc_core::Timestamp deadline) {
120   const int port = grpc_sockaddr_get_port(addr);
121   gpr_log(GPR_INFO, "==> HandleConnection(): port=%d", port);
122   {
123     grpc_core::MutexLock lock(&mu_);
124     // First, check if there's a hold request for this port.
125     for (auto it = holds_.begin(); it != holds_.end(); ++it) {
126       Hold* hold = *it;
127       if (port == hold->port_) {
128         gpr_log(GPR_INFO, "*** INTERCEPTING CONNECTION ATTEMPT");
129         if (hold->intercept_completion_) {
130           hold->original_on_complete_ = closure;
131           closure = GRPC_CLOSURE_INIT(&hold->on_complete_, Hold::OnComplete,
132                                       hold, nullptr);
133         }
134         hold->queued_attempt_ = std::make_unique<QueuedAttempt>(
135             closure, ep, interested_parties, config, addr, deadline);
136         hold->start_cv_.Signal();
137         holds_.erase(it);
138         return;
139       }
140     }
141     // Otherwise, if there's a configured delay, impose it.
142     if (delay_.has_value()) {
143       new InjectedDelay(*delay_, closure, ep, interested_parties, config, addr,
144                         deadline);
145       return;
146     }
147   }
148   // Anything we're not holding or delaying should proceed normally.
149   g_original_vtable->connect(closure, ep, interested_parties, config, addr,
150                              deadline);
151 }
152 
153 //
154 // ConnectionAttemptInjector::QueuedAttempt
155 //
156 
QueuedAttempt(grpc_closure * closure,grpc_endpoint ** ep,grpc_pollset_set * interested_parties,const EndpointConfig & config,const grpc_resolved_address * addr,grpc_core::Timestamp deadline)157 ConnectionAttemptInjector::QueuedAttempt::QueuedAttempt(
158     grpc_closure* closure, grpc_endpoint** ep,
159     grpc_pollset_set* interested_parties, const EndpointConfig& config,
160     const grpc_resolved_address* addr, grpc_core::Timestamp deadline)
161     : closure_(closure),
162       endpoint_(ep),
163       interested_parties_(interested_parties),
164       config_(*reinterpret_cast<const grpc_event_engine::experimental::
165                                     ChannelArgsEndpointConfig*>(&config)),
166       deadline_(deadline) {
167   memcpy(&address_, addr, sizeof(address_));
168 }
169 
~QueuedAttempt()170 ConnectionAttemptInjector::QueuedAttempt::~QueuedAttempt() {
171   GPR_ASSERT(closure_ == nullptr);
172 }
173 
Resume()174 void ConnectionAttemptInjector::QueuedAttempt::Resume() {
175   GPR_ASSERT(closure_ != nullptr);
176   g_original_vtable->connect(closure_, endpoint_, interested_parties_, config_,
177                              &address_, deadline_);
178   closure_ = nullptr;
179 }
180 
Fail(grpc_error_handle error)181 void ConnectionAttemptInjector::QueuedAttempt::Fail(grpc_error_handle error) {
182   GPR_ASSERT(closure_ != nullptr);
183   grpc_core::ExecCtx::Run(DEBUG_LOCATION, closure_, error);
184   closure_ = nullptr;
185 }
186 
187 //
188 // ConnectionAttemptInjector::InjectedDelay
189 //
190 
InjectedDelay(grpc_core::Duration duration,grpc_closure * closure,grpc_endpoint ** ep,grpc_pollset_set * interested_parties,const EndpointConfig & config,const grpc_resolved_address * addr,grpc_core::Timestamp deadline)191 ConnectionAttemptInjector::InjectedDelay::InjectedDelay(
192     grpc_core::Duration duration, grpc_closure* closure, grpc_endpoint** ep,
193     grpc_pollset_set* interested_parties, const EndpointConfig& config,
194     const grpc_resolved_address* addr, grpc_core::Timestamp deadline)
195     : attempt_(closure, ep, interested_parties, config, addr, deadline) {
196   grpc_core::Timestamp now = grpc_core::Timestamp::Now();
197   duration = std::min(duration, deadline - now);
198   grpc_event_engine::experimental::GetDefaultEventEngine()->RunAfter(
199       duration, [this] {
200         grpc_core::ApplicationCallbackExecCtx callback_exec_ctx;
201         grpc_core::ExecCtx exec_ctx;
202         TimerCallback();
203       });
204 }
205 
TimerCallback()206 void ConnectionAttemptInjector::InjectedDelay::TimerCallback() {
207   attempt_.Resume();
208   delete this;
209 }
210 
211 //
212 // ConnectionAttemptInjector::Hold
213 //
214 
Hold(ConnectionAttemptInjector * injector,int port,bool intercept_completion)215 ConnectionAttemptInjector::Hold::Hold(ConnectionAttemptInjector* injector,
216                                       int port, bool intercept_completion)
217     : injector_(injector),
218       port_(port),
219       intercept_completion_(intercept_completion) {}
220 
Wait()221 void ConnectionAttemptInjector::Hold::Wait() {
222   gpr_log(GPR_INFO, "=== WAITING FOR CONNECTION ATTEMPT ON PORT %d ===", port_);
223   grpc_core::MutexLock lock(&injector_->mu_);
224   while (queued_attempt_ == nullptr) {
225     start_cv_.Wait(&injector_->mu_);
226   }
227   gpr_log(GPR_INFO, "=== CONNECTION ATTEMPT STARTED ON PORT %d ===", port_);
228 }
229 
Resume()230 void ConnectionAttemptInjector::Hold::Resume() {
231   gpr_log(GPR_INFO, "=== RESUMING CONNECTION ATTEMPT ON PORT %d ===", port_);
232   grpc_core::ExecCtx exec_ctx;
233   std::unique_ptr<QueuedAttempt> attempt;
234   {
235     grpc_core::MutexLock lock(&injector_->mu_);
236     attempt = std::move(queued_attempt_);
237   }
238   attempt->Resume();
239 }
240 
Fail(grpc_error_handle error)241 void ConnectionAttemptInjector::Hold::Fail(grpc_error_handle error) {
242   gpr_log(GPR_INFO, "=== FAILING CONNECTION ATTEMPT ON PORT %d ===", port_);
243   grpc_core::ExecCtx exec_ctx;
244   std::unique_ptr<QueuedAttempt> attempt;
245   {
246     grpc_core::MutexLock lock(&injector_->mu_);
247     attempt = std::move(queued_attempt_);
248   }
249   attempt->Fail(error);
250 }
251 
WaitForCompletion()252 void ConnectionAttemptInjector::Hold::WaitForCompletion() {
253   gpr_log(GPR_INFO,
254           "=== WAITING FOR CONNECTION COMPLETION ON PORT %d ===", port_);
255   grpc_core::MutexLock lock(&injector_->mu_);
256   while (original_on_complete_ != nullptr) {
257     complete_cv_.Wait(&injector_->mu_);
258   }
259   gpr_log(GPR_INFO, "=== CONNECTION COMPLETED ON PORT %d ===", port_);
260 }
261 
IsStarted()262 bool ConnectionAttemptInjector::Hold::IsStarted() {
263   grpc_core::MutexLock lock(&injector_->mu_);
264   return !start_cv_.WaitWithDeadline(&injector_->mu_, absl::Now());
265 }
266 
OnComplete(void * arg,grpc_error_handle error)267 void ConnectionAttemptInjector::Hold::OnComplete(void* arg,
268                                                  grpc_error_handle error) {
269   auto* self = static_cast<Hold*>(arg);
270   grpc_closure* on_complete;
271   {
272     grpc_core::MutexLock lock(&self->injector_->mu_);
273     on_complete = self->original_on_complete_;
274     self->original_on_complete_ = nullptr;
275     self->complete_cv_.Signal();
276   }
277   grpc_core::Closure::Run(DEBUG_LOCATION, on_complete, error);
278 }
279 
280 }  // namespace testing
281 }  // namespace grpc
282