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