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