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