1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/socket/websocket_transport_client_socket_pool.h"
6
7 #include <algorithm>
8
9 #include "base/check_op.h"
10 #include "base/compiler_specific.h"
11 #include "base/functional/bind.h"
12 #include "base/functional/callback_helpers.h"
13 #include "base/location.h"
14 #include "base/notreached.h"
15 #include "base/numerics/safe_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/task/single_thread_task_runner.h"
18 #include "base/values.h"
19 #include "net/base/net_errors.h"
20 #include "net/log/net_log_event_type.h"
21 #include "net/log/net_log_source.h"
22 #include "net/log/net_log_source_type.h"
23 #include "net/socket/client_socket_handle.h"
24 #include "net/socket/connect_job.h"
25 #include "net/socket/connect_job_factory.h"
26 #include "net/socket/websocket_endpoint_lock_manager.h"
27 #include "net/traffic_annotation/network_traffic_annotation.h"
28
29 namespace net {
30
WebSocketTransportClientSocketPool(int max_sockets,int max_sockets_per_group,const ProxyChain & proxy_chain,const CommonConnectJobParams * common_connect_job_params)31 WebSocketTransportClientSocketPool::WebSocketTransportClientSocketPool(
32 int max_sockets,
33 int max_sockets_per_group,
34 const ProxyChain& proxy_chain,
35 const CommonConnectJobParams* common_connect_job_params)
36 : ClientSocketPool(/*is_for_websockets=*/true,
37 common_connect_job_params,
38 std::make_unique<ConnectJobFactory>()),
39 proxy_chain_(proxy_chain),
40 max_sockets_(max_sockets) {
41 DCHECK(common_connect_job_params->websocket_endpoint_lock_manager);
42 }
43
~WebSocketTransportClientSocketPool()44 WebSocketTransportClientSocketPool::~WebSocketTransportClientSocketPool() {
45 // Clean up any pending connect jobs.
46 FlushWithError(ERR_ABORTED, "");
47 CHECK(pending_connects_.empty());
48 CHECK_EQ(0, handed_out_socket_count_);
49 CHECK(stalled_request_queue_.empty());
50 CHECK(stalled_request_map_.empty());
51 }
52
53 // static
UnlockEndpoint(ClientSocketHandle * handle,WebSocketEndpointLockManager * websocket_endpoint_lock_manager)54 void WebSocketTransportClientSocketPool::UnlockEndpoint(
55 ClientSocketHandle* handle,
56 WebSocketEndpointLockManager* websocket_endpoint_lock_manager) {
57 DCHECK(handle->is_initialized());
58 DCHECK(handle->socket());
59 IPEndPoint address;
60 if (handle->socket()->GetPeerAddress(&address) == OK)
61 websocket_endpoint_lock_manager->UnlockEndpoint(address);
62 }
63
RequestSocket(const GroupId & group_id,scoped_refptr<SocketParams> params,const std::optional<NetworkTrafficAnnotationTag> & proxy_annotation_tag,RequestPriority priority,const SocketTag & socket_tag,RespectLimits respect_limits,ClientSocketHandle * handle,CompletionOnceCallback callback,const ProxyAuthCallback & proxy_auth_callback,const NetLogWithSource & request_net_log)64 int WebSocketTransportClientSocketPool::RequestSocket(
65 const GroupId& group_id,
66 scoped_refptr<SocketParams> params,
67 const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
68 RequestPriority priority,
69 const SocketTag& socket_tag,
70 RespectLimits respect_limits,
71 ClientSocketHandle* handle,
72 CompletionOnceCallback callback,
73 const ProxyAuthCallback& proxy_auth_callback,
74 const NetLogWithSource& request_net_log) {
75 DCHECK(params);
76 CHECK(!callback.is_null());
77 CHECK(handle);
78 DCHECK(socket_tag == SocketTag());
79
80 NetLogTcpClientSocketPoolRequestedSocket(request_net_log, group_id);
81 request_net_log.BeginEvent(NetLogEventType::SOCKET_POOL);
82
83 if (ReachedMaxSocketsLimit() &&
84 respect_limits == ClientSocketPool::RespectLimits::ENABLED) {
85 request_net_log.AddEvent(NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS);
86 stalled_request_queue_.emplace_back(group_id, params, proxy_annotation_tag,
87 priority, handle, std::move(callback),
88 proxy_auth_callback, request_net_log);
89 auto iterator = stalled_request_queue_.end();
90 --iterator;
91 DCHECK_EQ(handle, iterator->handle);
92 // Because StalledRequestQueue is a std::list, its iterators are guaranteed
93 // to remain valid as long as the elements are not removed. As long as
94 // stalled_request_queue_ and stalled_request_map_ are updated in sync, it
95 // is safe to dereference an iterator in stalled_request_map_ to find the
96 // corresponding list element.
97 stalled_request_map_.insert(
98 StalledRequestMap::value_type(handle, iterator));
99 return ERR_IO_PENDING;
100 }
101
102 std::unique_ptr<ConnectJobDelegate> connect_job_delegate =
103 std::make_unique<ConnectJobDelegate>(this, std::move(callback), handle,
104 request_net_log);
105
106 std::unique_ptr<ConnectJob> connect_job =
107 CreateConnectJob(group_id, params, proxy_chain_, proxy_annotation_tag,
108 priority, SocketTag(), connect_job_delegate.get());
109
110 int result = connect_job_delegate->Connect(std::move(connect_job));
111
112 // Regardless of the outcome of |connect_job|, it will always be bound to
113 // |handle|, since this pool uses early-binding. So the binding is logged
114 // here, without waiting for the result.
115 request_net_log.AddEventReferencingSource(
116 NetLogEventType::SOCKET_POOL_BOUND_TO_CONNECT_JOB,
117 connect_job_delegate->connect_job_net_log().source());
118
119 if (result == ERR_IO_PENDING) {
120 // TODO(ricea): Implement backup job timer?
121 AddJob(handle, std::move(connect_job_delegate));
122 } else {
123 TryHandOutSocket(result, connect_job_delegate.get());
124 }
125
126 return result;
127 }
128
RequestSockets(const GroupId & group_id,scoped_refptr<SocketParams> params,const std::optional<NetworkTrafficAnnotationTag> & proxy_annotation_tag,int num_sockets,CompletionOnceCallback callback,const NetLogWithSource & net_log)129 int WebSocketTransportClientSocketPool::RequestSockets(
130 const GroupId& group_id,
131 scoped_refptr<SocketParams> params,
132 const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
133 int num_sockets,
134 CompletionOnceCallback callback,
135 const NetLogWithSource& net_log) {
136 NOTIMPLEMENTED();
137 return OK;
138 }
139
SetPriority(const GroupId & group_id,ClientSocketHandle * handle,RequestPriority priority)140 void WebSocketTransportClientSocketPool::SetPriority(const GroupId& group_id,
141 ClientSocketHandle* handle,
142 RequestPriority priority) {
143 // Since sockets requested by RequestSocket are bound early and
144 // stalled_request_{queue,map} don't take priorities into account, there's
145 // nothing to do within the pool to change priority of the request.
146 // TODO(rdsmith, ricea): Make stalled_request_{queue,map} take priorities
147 // into account.
148 // TODO(rdsmith, chlily): Investigate plumbing the reprioritization request to
149 // the connect job.
150 }
151
CancelRequest(const GroupId & group_id,ClientSocketHandle * handle,bool cancel_connect_job)152 void WebSocketTransportClientSocketPool::CancelRequest(
153 const GroupId& group_id,
154 ClientSocketHandle* handle,
155 bool cancel_connect_job) {
156 DCHECK(!handle->is_initialized());
157 if (DeleteStalledRequest(handle))
158 return;
159 std::unique_ptr<StreamSocket> socket = handle->PassSocket();
160 if (socket)
161 ReleaseSocket(handle->group_id(), std::move(socket),
162 handle->group_generation());
163 if (DeleteJob(handle)) {
164 CHECK(!base::Contains(pending_callbacks_,
165 reinterpret_cast<ClientSocketHandleID>(handle)));
166 } else {
167 pending_callbacks_.erase(reinterpret_cast<ClientSocketHandleID>(handle));
168 }
169
170 ActivateStalledRequest();
171 }
172
ReleaseSocket(const GroupId & group_id,std::unique_ptr<StreamSocket> socket,int64_t generation)173 void WebSocketTransportClientSocketPool::ReleaseSocket(
174 const GroupId& group_id,
175 std::unique_ptr<StreamSocket> socket,
176 int64_t generation) {
177 CHECK_GT(handed_out_socket_count_, 0);
178 --handed_out_socket_count_;
179
180 ActivateStalledRequest();
181 }
182
FlushWithError(int error,const char * net_log_reason_utf8)183 void WebSocketTransportClientSocketPool::FlushWithError(
184 int error,
185 const char* net_log_reason_utf8) {
186 DCHECK_NE(error, OK);
187
188 // Sockets which are in LOAD_STATE_CONNECTING are in danger of unlocking
189 // sockets waiting for the endpoint lock. If they connected synchronously,
190 // then OnConnectJobComplete(). The |flushing_| flag tells this object to
191 // ignore spurious calls to OnConnectJobComplete(). It is safe to ignore those
192 // calls because this method will delete the jobs and call their callbacks
193 // anyway.
194 flushing_ = true;
195 for (auto it = pending_connects_.begin(); it != pending_connects_.end();) {
196 InvokeUserCallbackLater(it->second->socket_handle(),
197 it->second->release_callback(), error);
198 it->second->connect_job_net_log().AddEventWithStringParams(
199 NetLogEventType::SOCKET_POOL_CLOSING_SOCKET, "reason",
200 net_log_reason_utf8);
201 it = pending_connects_.erase(it);
202 }
203 for (auto& stalled_request : stalled_request_queue_) {
204 InvokeUserCallbackLater(stalled_request.handle,
205 std::move(stalled_request.callback), error);
206 }
207 stalled_request_map_.clear();
208 stalled_request_queue_.clear();
209 flushing_ = false;
210 }
211
CloseIdleSockets(const char * net_log_reason_utf8)212 void WebSocketTransportClientSocketPool::CloseIdleSockets(
213 const char* net_log_reason_utf8) {
214 // We have no idle sockets.
215 }
216
CloseIdleSocketsInGroup(const GroupId & group_id,const char * net_log_reason_utf8)217 void WebSocketTransportClientSocketPool::CloseIdleSocketsInGroup(
218 const GroupId& group_id,
219 const char* net_log_reason_utf8) {
220 // We have no idle sockets.
221 }
222
IdleSocketCount() const223 int WebSocketTransportClientSocketPool::IdleSocketCount() const {
224 return 0;
225 }
226
IdleSocketCountInGroup(const GroupId & group_id) const227 size_t WebSocketTransportClientSocketPool::IdleSocketCountInGroup(
228 const GroupId& group_id) const {
229 return 0;
230 }
231
GetLoadState(const GroupId & group_id,const ClientSocketHandle * handle) const232 LoadState WebSocketTransportClientSocketPool::GetLoadState(
233 const GroupId& group_id,
234 const ClientSocketHandle* handle) const {
235 if (stalled_request_map_.find(handle) != stalled_request_map_.end())
236 return LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET;
237 if (pending_callbacks_.count(reinterpret_cast<ClientSocketHandleID>(handle)))
238 return LOAD_STATE_CONNECTING;
239 return LookupConnectJob(handle)->GetLoadState();
240 }
241
GetInfoAsValue(const std::string & name,const std::string & type) const242 base::Value WebSocketTransportClientSocketPool::GetInfoAsValue(
243 const std::string& name,
244 const std::string& type) const {
245 auto dict = base::Value::Dict()
246 .Set("name", name)
247 .Set("type", type)
248 .Set("handed_out_socket_count", handed_out_socket_count_)
249 .Set("connecting_socket_count",
250 static_cast<int>(pending_connects_.size()))
251 .Set("idle_socket_count", 0)
252 .Set("max_socket_count", max_sockets_)
253 .Set("max_sockets_per_group", max_sockets_);
254 return base::Value(std::move(dict));
255 }
256
HasActiveSocket(const GroupId & group_id) const257 bool WebSocketTransportClientSocketPool::HasActiveSocket(
258 const GroupId& group_id) const {
259 // This method is not supported for WebSocket.
260 NOTREACHED();
261 return false;
262 }
263
IsStalled() const264 bool WebSocketTransportClientSocketPool::IsStalled() const {
265 return !stalled_request_queue_.empty();
266 }
267
AddHigherLayeredPool(HigherLayeredPool * higher_pool)268 void WebSocketTransportClientSocketPool::AddHigherLayeredPool(
269 HigherLayeredPool* higher_pool) {
270 // This class doesn't use connection limits like the pools for HTTP do, so no
271 // need to track higher layered pools.
272 }
273
RemoveHigherLayeredPool(HigherLayeredPool * higher_pool)274 void WebSocketTransportClientSocketPool::RemoveHigherLayeredPool(
275 HigherLayeredPool* higher_pool) {
276 // This class doesn't use connection limits like the pools for HTTP do, so no
277 // need to track higher layered pools.
278 }
279
TryHandOutSocket(int result,ConnectJobDelegate * connect_job_delegate)280 bool WebSocketTransportClientSocketPool::TryHandOutSocket(
281 int result,
282 ConnectJobDelegate* connect_job_delegate) {
283 DCHECK_NE(result, ERR_IO_PENDING);
284
285 std::unique_ptr<StreamSocket> socket =
286 connect_job_delegate->connect_job()->PassSocket();
287 LoadTimingInfo::ConnectTiming connect_timing =
288 connect_job_delegate->connect_job()->connect_timing();
289 ClientSocketHandle* const handle = connect_job_delegate->socket_handle();
290 NetLogWithSource request_net_log = connect_job_delegate->request_net_log();
291
292 if (result == OK) {
293 DCHECK(socket);
294
295 HandOutSocket(std::move(socket), connect_timing, handle, request_net_log);
296
297 request_net_log.EndEvent(NetLogEventType::SOCKET_POOL);
298
299 return true;
300 }
301
302 bool handed_out_socket = false;
303
304 // If we got a socket, it must contain error information so pass that
305 // up so that the caller can retrieve it.
306 handle->SetAdditionalErrorState(connect_job_delegate->connect_job());
307 if (socket) {
308 HandOutSocket(std::move(socket), connect_timing, handle, request_net_log);
309 handed_out_socket = true;
310 }
311
312 request_net_log.EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
313 result);
314
315 return handed_out_socket;
316 }
317
OnConnectJobComplete(int result,ConnectJobDelegate * connect_job_delegate)318 void WebSocketTransportClientSocketPool::OnConnectJobComplete(
319 int result,
320 ConnectJobDelegate* connect_job_delegate) {
321 DCHECK_NE(ERR_IO_PENDING, result);
322
323 // See comment in FlushWithError.
324 if (flushing_) {
325 // Just delete the socket.
326 std::unique_ptr<StreamSocket> socket =
327 connect_job_delegate->connect_job()->PassSocket();
328 return;
329 }
330
331 bool handed_out_socket = TryHandOutSocket(result, connect_job_delegate);
332
333 CompletionOnceCallback callback = connect_job_delegate->release_callback();
334
335 ClientSocketHandle* const handle = connect_job_delegate->socket_handle();
336
337 bool delete_succeeded = DeleteJob(handle);
338 CHECK(delete_succeeded);
339
340 connect_job_delegate = nullptr;
341
342 if (!handed_out_socket)
343 ActivateStalledRequest();
344
345 InvokeUserCallbackLater(handle, std::move(callback), result);
346 }
347
InvokeUserCallbackLater(ClientSocketHandle * handle,CompletionOnceCallback callback,int rv)348 void WebSocketTransportClientSocketPool::InvokeUserCallbackLater(
349 ClientSocketHandle* handle,
350 CompletionOnceCallback callback,
351 int rv) {
352 const auto handle_id = reinterpret_cast<ClientSocketHandleID>(handle);
353 CHECK(!pending_callbacks_.count(handle_id));
354 pending_callbacks_.insert(handle_id);
355 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
356 FROM_HERE,
357 base::BindOnce(&WebSocketTransportClientSocketPool::InvokeUserCallback,
358 weak_factory_.GetWeakPtr(), handle_id,
359 handle->GetWeakPtr(), std::move(callback), rv));
360 }
361
InvokeUserCallback(ClientSocketHandleID handle_id,base::WeakPtr<ClientSocketHandle> weak_handle,CompletionOnceCallback callback,int rv)362 void WebSocketTransportClientSocketPool::InvokeUserCallback(
363 ClientSocketHandleID handle_id,
364 base::WeakPtr<ClientSocketHandle> weak_handle,
365 CompletionOnceCallback callback,
366 int rv) {
367 if (pending_callbacks_.erase(handle_id)) {
368 CHECK(weak_handle);
369 std::move(callback).Run(rv);
370 }
371 }
372
ReachedMaxSocketsLimit() const373 bool WebSocketTransportClientSocketPool::ReachedMaxSocketsLimit() const {
374 return handed_out_socket_count_ >= max_sockets_ ||
375 base::checked_cast<int>(pending_connects_.size()) >=
376 max_sockets_ - handed_out_socket_count_;
377 }
378
HandOutSocket(std::unique_ptr<StreamSocket> socket,const LoadTimingInfo::ConnectTiming & connect_timing,ClientSocketHandle * handle,const NetLogWithSource & net_log)379 void WebSocketTransportClientSocketPool::HandOutSocket(
380 std::unique_ptr<StreamSocket> socket,
381 const LoadTimingInfo::ConnectTiming& connect_timing,
382 ClientSocketHandle* handle,
383 const NetLogWithSource& net_log) {
384 DCHECK(socket);
385 DCHECK_EQ(ClientSocketHandle::UNUSED, handle->reuse_type());
386 DCHECK_EQ(0, handle->idle_time().InMicroseconds());
387
388 handle->SetSocket(std::move(socket));
389 handle->set_group_generation(0);
390 handle->set_connect_timing(connect_timing);
391
392 net_log.AddEventReferencingSource(
393 NetLogEventType::SOCKET_POOL_BOUND_TO_SOCKET,
394 handle->socket()->NetLog().source());
395
396 ++handed_out_socket_count_;
397 }
398
AddJob(ClientSocketHandle * handle,std::unique_ptr<ConnectJobDelegate> delegate)399 void WebSocketTransportClientSocketPool::AddJob(
400 ClientSocketHandle* handle,
401 std::unique_ptr<ConnectJobDelegate> delegate) {
402 bool inserted =
403 pending_connects_
404 .insert(PendingConnectsMap::value_type(handle, std::move(delegate)))
405 .second;
406 CHECK(inserted);
407 }
408
DeleteJob(ClientSocketHandle * handle)409 bool WebSocketTransportClientSocketPool::DeleteJob(ClientSocketHandle* handle) {
410 auto it = pending_connects_.find(handle);
411 if (it == pending_connects_.end())
412 return false;
413 // Deleting a ConnectJob which holds an endpoint lock can lead to a different
414 // ConnectJob proceeding to connect. If the connect proceeds synchronously
415 // (usually because of a failure) then it can trigger that job to be
416 // deleted.
417 pending_connects_.erase(it);
418 return true;
419 }
420
LookupConnectJob(const ClientSocketHandle * handle) const421 const ConnectJob* WebSocketTransportClientSocketPool::LookupConnectJob(
422 const ClientSocketHandle* handle) const {
423 auto it = pending_connects_.find(handle);
424 CHECK(it != pending_connects_.end());
425 return it->second->connect_job();
426 }
427
ActivateStalledRequest()428 void WebSocketTransportClientSocketPool::ActivateStalledRequest() {
429 // Usually we will only be able to activate one stalled request at a time,
430 // however if all the connects fail synchronously for some reason, we may be
431 // able to clear the whole queue at once.
432 while (!stalled_request_queue_.empty() && !ReachedMaxSocketsLimit()) {
433 StalledRequest request = std::move(stalled_request_queue_.front());
434 stalled_request_queue_.pop_front();
435 stalled_request_map_.erase(request.handle);
436
437 auto split_callback = base::SplitOnceCallback(std::move(request.callback));
438
439 int rv = RequestSocket(
440 request.group_id, request.params, request.proxy_annotation_tag,
441 request.priority, SocketTag(),
442 // Stalled requests can't have |respect_limits|
443 // DISABLED.
444 RespectLimits::ENABLED, request.handle, std::move(split_callback.first),
445 request.proxy_auth_callback, request.net_log);
446
447 // ActivateStalledRequest() never returns synchronously, so it is never
448 // called re-entrantly.
449 if (rv != ERR_IO_PENDING)
450 InvokeUserCallbackLater(request.handle, std::move(split_callback.second),
451 rv);
452 }
453 }
454
DeleteStalledRequest(ClientSocketHandle * handle)455 bool WebSocketTransportClientSocketPool::DeleteStalledRequest(
456 ClientSocketHandle* handle) {
457 auto it = stalled_request_map_.find(handle);
458 if (it == stalled_request_map_.end())
459 return false;
460 stalled_request_queue_.erase(it->second);
461 stalled_request_map_.erase(it);
462 return true;
463 }
464
ConnectJobDelegate(WebSocketTransportClientSocketPool * owner,CompletionOnceCallback callback,ClientSocketHandle * socket_handle,const NetLogWithSource & request_net_log)465 WebSocketTransportClientSocketPool::ConnectJobDelegate::ConnectJobDelegate(
466 WebSocketTransportClientSocketPool* owner,
467 CompletionOnceCallback callback,
468 ClientSocketHandle* socket_handle,
469 const NetLogWithSource& request_net_log)
470 : owner_(owner),
471 callback_(std::move(callback)),
472 socket_handle_(socket_handle),
473 request_net_log_(request_net_log) {}
474
475 WebSocketTransportClientSocketPool::ConnectJobDelegate::~ConnectJobDelegate() =
476 default;
477
478 void
OnConnectJobComplete(int result,ConnectJob * job)479 WebSocketTransportClientSocketPool::ConnectJobDelegate::OnConnectJobComplete(
480 int result,
481 ConnectJob* job) {
482 DCHECK_EQ(job, connect_job_.get());
483 owner_->OnConnectJobComplete(result, this);
484 }
485
OnNeedsProxyAuth(const HttpResponseInfo & response,HttpAuthController * auth_controller,base::OnceClosure restart_with_auth_callback,ConnectJob * job)486 void WebSocketTransportClientSocketPool::ConnectJobDelegate::OnNeedsProxyAuth(
487 const HttpResponseInfo& response,
488 HttpAuthController* auth_controller,
489 base::OnceClosure restart_with_auth_callback,
490 ConnectJob* job) {
491 // This class isn't used for proxies.
492 NOTREACHED();
493 }
494
Connect(std::unique_ptr<ConnectJob> connect_job)495 int WebSocketTransportClientSocketPool::ConnectJobDelegate::Connect(
496 std::unique_ptr<ConnectJob> connect_job) {
497 connect_job_ = std::move(connect_job);
498 return connect_job_->Connect();
499 }
500
501 const NetLogWithSource&
connect_job_net_log()502 WebSocketTransportClientSocketPool::ConnectJobDelegate::connect_job_net_log() {
503 return connect_job_->net_log();
504 }
505
StalledRequest(const GroupId & group_id,const scoped_refptr<SocketParams> & params,const std::optional<NetworkTrafficAnnotationTag> & proxy_annotation_tag,RequestPriority priority,ClientSocketHandle * handle,CompletionOnceCallback callback,const ProxyAuthCallback & proxy_auth_callback,const NetLogWithSource & net_log)506 WebSocketTransportClientSocketPool::StalledRequest::StalledRequest(
507 const GroupId& group_id,
508 const scoped_refptr<SocketParams>& params,
509 const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
510 RequestPriority priority,
511 ClientSocketHandle* handle,
512 CompletionOnceCallback callback,
513 const ProxyAuthCallback& proxy_auth_callback,
514 const NetLogWithSource& net_log)
515 : group_id(group_id),
516 params(params),
517 proxy_annotation_tag(proxy_annotation_tag),
518 priority(priority),
519 handle(handle),
520 callback(std::move(callback)),
521 proxy_auth_callback(proxy_auth_callback),
522 net_log(net_log) {}
523
524 WebSocketTransportClientSocketPool::StalledRequest::StalledRequest(
525 StalledRequest&& other) = default;
526
527 WebSocketTransportClientSocketPool::StalledRequest::~StalledRequest() = default;
528
529 } // namespace net
530