Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2016 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/http/http_stream_factory_impl_job_controller.h" | 5 #include "net/http/http_stream_factory_impl_job_controller.h" |
| 6 | 6 |
| 7 #include "base/metrics/histogram_macros.h" | 7 #include "base/metrics/histogram_macros.h" |
| 8 #include "base/strings/string_number_conversions.h" | 8 #include "base/strings/string_number_conversions.h" |
| 9 #include "base/strings/string_util.h" | 9 #include "base/strings/string_util.h" |
| 10 #include "base/values.h" | |
| 10 #include "net/base/host_mapping_rules.h" | 11 #include "net/base/host_mapping_rules.h" |
| 11 #include "net/http/bidirectional_stream_impl.h" | 12 #include "net/http/bidirectional_stream_impl.h" |
| 12 #include "net/http/transport_security_state.h" | 13 #include "net/http/transport_security_state.h" |
| 13 #include "net/spdy/spdy_session.h" | 14 #include "net/spdy/spdy_session.h" |
| 14 | 15 |
| 15 namespace net { | 16 namespace net { |
| 16 | 17 |
| 18 // Returns parameters associated with the delay of the HTTP stream job. | |
| 19 std::unique_ptr<base::Value> NetLogHttpStreamJobDelayCallback( | |
| 20 base::TimeDelta delay, | |
| 21 NetLogCaptureMode /* capture_mode */) { | |
| 22 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
| 23 dict->SetInteger("resume_after_ms", static_cast<int>(delay.InMilliseconds())); | |
| 24 return std::move(dict); | |
| 25 } | |
| 26 | |
| 17 HttpStreamFactoryImpl::JobController::JobController( | 27 HttpStreamFactoryImpl::JobController::JobController( |
| 18 HttpStreamFactoryImpl* factory, | 28 HttpStreamFactoryImpl* factory, |
| 19 HttpStreamRequest::Delegate* delegate, | 29 HttpStreamRequest::Delegate* delegate, |
| 20 HttpNetworkSession* session, | 30 HttpNetworkSession* session, |
| 21 JobFactory* job_factory) | 31 JobFactory* job_factory) |
| 22 : factory_(factory), | 32 : factory_(factory), |
| 23 session_(session), | 33 session_(session), |
| 24 job_factory_(job_factory), | 34 job_factory_(job_factory), |
| 25 request_(nullptr), | 35 request_(nullptr), |
| 26 delegate_(delegate), | 36 delegate_(delegate), |
| 27 is_preconnect_(false), | 37 is_preconnect_(false), |
| 28 job_bound_(false), | 38 job_bound_(false), |
| 29 bound_job_(nullptr) { | 39 main_job_is_blocked_(false), |
| 40 bound_job_(nullptr), | |
| 41 ptr_factory_(this) { | |
| 30 DCHECK(factory); | 42 DCHECK(factory); |
| 31 } | 43 } |
| 32 | 44 |
| 33 HttpStreamFactoryImpl::JobController::~JobController() { | 45 HttpStreamFactoryImpl::JobController::~JobController() { |
| 34 main_job_.reset(); | 46 main_job_.reset(); |
| 35 alternative_job_.reset(); | 47 alternative_job_.reset(); |
| 36 bound_job_ = nullptr; | 48 bound_job_ = nullptr; |
| 37 } | 49 } |
| 38 | 50 |
| 39 bool HttpStreamFactoryImpl::JobController::for_websockets() { | 51 bool HttpStreamFactoryImpl::JobController::for_websockets() { |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 216 OnJobSucceeded(job); | 228 OnJobSucceeded(job); |
| 217 request_->OnWebSocketHandshakeStreamReady(used_ssl_config, used_proxy_info, | 229 request_->OnWebSocketHandshakeStreamReady(used_ssl_config, used_proxy_info, |
| 218 stream); | 230 stream); |
| 219 } | 231 } |
| 220 | 232 |
| 221 void HttpStreamFactoryImpl::JobController::OnStreamFailed( | 233 void HttpStreamFactoryImpl::JobController::OnStreamFailed( |
| 222 Job* job, | 234 Job* job, |
| 223 int status, | 235 int status, |
| 224 const SSLConfig& used_ssl_config, | 236 const SSLConfig& used_ssl_config, |
| 225 SSLFailureState ssl_failure_state) { | 237 SSLFailureState ssl_failure_state) { |
| 238 MaybeResumeMainJob(job, base::TimeDelta()); | |
| 239 | |
| 226 if (job_bound_ && bound_job_ != job) { | 240 if (job_bound_ && bound_job_ != job) { |
| 227 // We have bound a job to the associated Request, |job| has been orphaned. | 241 // We have bound a job to the associated Request, |job| has been orphaned. |
| 228 OnOrphanedJobComplete(job); | 242 OnOrphanedJobComplete(job); |
| 229 return; | 243 return; |
| 230 } | 244 } |
| 231 | 245 |
| 232 if (!request_) | 246 if (!request_) |
| 233 return; | 247 return; |
| 234 DCHECK_NE(OK, status); | 248 DCHECK_NE(OK, status); |
| 235 DCHECK(job); | 249 DCHECK(job); |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 255 } | 269 } |
| 256 | 270 |
| 257 request_->OnStreamFailed(status, used_ssl_config, ssl_failure_state); | 271 request_->OnStreamFailed(status, used_ssl_config, ssl_failure_state); |
| 258 } | 272 } |
| 259 | 273 |
| 260 void HttpStreamFactoryImpl::JobController::OnCertificateError( | 274 void HttpStreamFactoryImpl::JobController::OnCertificateError( |
| 261 Job* job, | 275 Job* job, |
| 262 int status, | 276 int status, |
| 263 const SSLConfig& used_ssl_config, | 277 const SSLConfig& used_ssl_config, |
| 264 const SSLInfo& ssl_info) { | 278 const SSLInfo& ssl_info) { |
| 279 MaybeResumeMainJob(job, base::TimeDelta()); | |
| 280 | |
| 265 if (job_bound_ && bound_job_ != job) { | 281 if (job_bound_ && bound_job_ != job) { |
| 266 // We have bound a job to the associated Request, |job| has been orphaned. | 282 // We have bound a job to the associated Request, |job| has been orphaned. |
| 267 OnOrphanedJobComplete(job); | 283 OnOrphanedJobComplete(job); |
| 268 return; | 284 return; |
| 269 } | 285 } |
| 270 | 286 |
| 271 if (!request_) | 287 if (!request_) |
| 272 return; | 288 return; |
| 273 DCHECK_NE(OK, status); | 289 DCHECK_NE(OK, status); |
| 274 if (!bound_job_) | 290 if (!bound_job_) |
| 275 BindJob(job); | 291 BindJob(job); |
| 276 | 292 |
| 277 request_->OnCertificateError(status, used_ssl_config, ssl_info); | 293 request_->OnCertificateError(status, used_ssl_config, ssl_info); |
| 278 } | 294 } |
| 279 | 295 |
| 280 void HttpStreamFactoryImpl::JobController::OnHttpsProxyTunnelResponse( | 296 void HttpStreamFactoryImpl::JobController::OnHttpsProxyTunnelResponse( |
| 281 Job* job, | 297 Job* job, |
| 282 const HttpResponseInfo& response_info, | 298 const HttpResponseInfo& response_info, |
| 283 const SSLConfig& used_ssl_config, | 299 const SSLConfig& used_ssl_config, |
| 284 const ProxyInfo& used_proxy_info, | 300 const ProxyInfo& used_proxy_info, |
| 285 HttpStream* stream) { | 301 HttpStream* stream) { |
| 302 MaybeResumeMainJob(job, base::TimeDelta()); | |
| 303 | |
| 286 if (job_bound_ && bound_job_ != job) { | 304 if (job_bound_ && bound_job_ != job) { |
| 287 // We have bound a job to the associated Request, |job| has been orphaned. | 305 // We have bound a job to the associated Request, |job| has been orphaned. |
| 288 OnOrphanedJobComplete(job); | 306 OnOrphanedJobComplete(job); |
| 289 return; | 307 return; |
| 290 } | 308 } |
| 291 | 309 |
| 292 if (!bound_job_) | 310 if (!bound_job_) |
| 293 BindJob(job); | 311 BindJob(job); |
| 294 if (!request_) | 312 if (!request_) |
| 295 return; | 313 return; |
| 296 request_->OnHttpsProxyTunnelResponse(response_info, used_ssl_config, | 314 request_->OnHttpsProxyTunnelResponse(response_info, used_ssl_config, |
| 297 used_proxy_info, stream); | 315 used_proxy_info, stream); |
| 298 } | 316 } |
| 299 | 317 |
| 300 void HttpStreamFactoryImpl::JobController::OnNeedsClientAuth( | 318 void HttpStreamFactoryImpl::JobController::OnNeedsClientAuth( |
| 301 Job* job, | 319 Job* job, |
| 302 const SSLConfig& used_ssl_config, | 320 const SSLConfig& used_ssl_config, |
| 303 SSLCertRequestInfo* cert_info) { | 321 SSLCertRequestInfo* cert_info) { |
| 322 MaybeResumeMainJob(job, base::TimeDelta()); | |
| 323 | |
| 304 if (job_bound_ && bound_job_ != job) { | 324 if (job_bound_ && bound_job_ != job) { |
| 305 // We have bound a job to the associated Request, |job| has been orphaned. | 325 // We have bound a job to the associated Request, |job| has been orphaned. |
| 306 OnOrphanedJobComplete(job); | 326 OnOrphanedJobComplete(job); |
| 307 return; | 327 return; |
| 308 } | 328 } |
| 309 if (!request_) | 329 if (!request_) |
| 310 return; | 330 return; |
| 311 if (!bound_job_) | 331 if (!bound_job_) |
| 312 BindJob(job); | 332 BindJob(job); |
| 313 | 333 |
| 314 request_->OnNeedsClientAuth(used_ssl_config, cert_info); | 334 request_->OnNeedsClientAuth(used_ssl_config, cert_info); |
| 315 } | 335 } |
| 316 | 336 |
| 317 void HttpStreamFactoryImpl::JobController::OnNeedsProxyAuth( | 337 void HttpStreamFactoryImpl::JobController::OnNeedsProxyAuth( |
| 318 Job* job, | 338 Job* job, |
| 319 const HttpResponseInfo& proxy_response, | 339 const HttpResponseInfo& proxy_response, |
| 320 const SSLConfig& used_ssl_config, | 340 const SSLConfig& used_ssl_config, |
| 321 const ProxyInfo& used_proxy_info, | 341 const ProxyInfo& used_proxy_info, |
| 322 HttpAuthController* auth_controller) { | 342 HttpAuthController* auth_controller) { |
| 343 MaybeResumeMainJob(job, base::TimeDelta()); | |
| 344 | |
| 323 if (job_bound_ && bound_job_ != job) { | 345 if (job_bound_ && bound_job_ != job) { |
| 324 // We have bound a job to the associated Request, |job| has been orphaned. | 346 // We have bound a job to the associated Request, |job| has been orphaned. |
| 325 OnOrphanedJobComplete(job); | 347 OnOrphanedJobComplete(job); |
| 326 return; | 348 return; |
| 327 } | 349 } |
| 328 | 350 |
| 329 if (!request_) | 351 if (!request_) |
| 330 return; | 352 return; |
| 331 if (!bound_job_) | 353 if (!bound_job_) |
| 332 BindJob(job); | 354 BindJob(job); |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 392 used_proxy_info, was_npn_negotiated, | 414 used_proxy_info, was_npn_negotiated, |
| 393 protocol_negotiated, using_spdy, net_log); | 415 protocol_negotiated, using_spdy, net_log); |
| 394 } | 416 } |
| 395 if (is_job_orphaned) { | 417 if (is_job_orphaned) { |
| 396 OnOrphanedJobComplete(job); | 418 OnOrphanedJobComplete(job); |
| 397 } | 419 } |
| 398 } | 420 } |
| 399 | 421 |
| 400 void HttpStreamFactoryImpl::JobController::OnPreconnectsComplete(Job* job) { | 422 void HttpStreamFactoryImpl::JobController::OnPreconnectsComplete(Job* job) { |
| 401 DCHECK_EQ(main_job_.get(), job); | 423 DCHECK_EQ(main_job_.get(), job); |
| 424 DCHECK(!bound_job_); | |
| 402 main_job_.reset(); | 425 main_job_.reset(); |
| 403 factory_->OnPreconnectsCompleteInternal(); | 426 factory_->OnPreconnectsCompleteInternal(); |
| 404 MaybeNotifyFactoryOfCompletion(); | 427 MaybeNotifyFactoryOfCompletion(); |
| 405 } | 428 } |
| 406 | 429 |
| 407 void HttpStreamFactoryImpl::JobController::OnOrphanedJobComplete( | 430 void HttpStreamFactoryImpl::JobController::OnOrphanedJobComplete( |
| 408 const Job* job) { | 431 const Job* job) { |
| 409 if (job->job_type() == MAIN) { | 432 if (job->job_type() == MAIN) { |
| 410 DCHECK_EQ(main_job_.get(), job); | 433 DCHECK_EQ(main_job_.get(), job); |
| 411 main_job_.reset(); | 434 main_job_.reset(); |
| 412 } else { | 435 } else { |
| 413 DCHECK_EQ(alternative_job_.get(), job); | 436 DCHECK_EQ(alternative_job_.get(), job); |
| 414 alternative_job_.reset(); | 437 alternative_job_.reset(); |
| 415 } | 438 } |
| 416 | 439 |
| 417 MaybeNotifyFactoryOfCompletion(); | 440 MaybeNotifyFactoryOfCompletion(); |
| 418 } | 441 } |
| 419 | 442 |
| 420 void HttpStreamFactoryImpl::JobController::AddConnectionAttemptsToRequest( | 443 void HttpStreamFactoryImpl::JobController::AddConnectionAttemptsToRequest( |
| 421 Job* job, | 444 Job* job, |
| 422 const ConnectionAttempts& attempts) { | 445 const ConnectionAttempts& attempts) { |
| 423 if (is_preconnect_ || (job_bound_ && bound_job_ != job)) | 446 if (is_preconnect_ || (job_bound_ && bound_job_ != job)) |
| 424 return; | 447 return; |
| 425 | 448 |
| 426 DCHECK(request_); | 449 DCHECK(request_); |
| 427 request_->AddConnectionAttempts(attempts); | 450 request_->AddConnectionAttempts(attempts); |
| 428 } | 451 } |
| 429 | 452 |
| 453 void HttpStreamFactoryImpl::JobController::ResumeMainJob() { | |
| 454 main_job_->net_log().AddEvent( | |
| 455 NetLog::TYPE_HTTP_STREAM_JOB_DELAYED, | |
| 456 base::Bind(&NetLogHttpStreamJobDelayCallback, main_job_wait_time_)); | |
| 457 | |
| 458 main_job_->Resume(); | |
| 459 main_job_wait_time_ = base::TimeDelta(); | |
| 460 } | |
| 461 | |
| 462 void HttpStreamFactoryImpl::JobController::MaybeResumeMainJob( | |
| 463 Job* job, | |
| 464 const base::TimeDelta& delay) { | |
| 465 DCHECK(job == main_job_.get() || job == alternative_job_.get()); | |
| 466 if (!main_job_is_blocked_) | |
| 467 return; | |
| 468 | |
| 469 if (job == alternative_job_.get() && main_job_) { | |
|
Ryan Hamilton
2016/07/12 00:21:45
nit: I recommend reversing the polarity of this ch
Zhongyi Shi
2016/07/12 23:03:12
Done.
| |
| 470 main_job_is_blocked_ = false; | |
| 471 if (main_job_->is_waiting()) { | |
| 472 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
| 473 FROM_HERE, | |
| 474 base::Bind(&HttpStreamFactoryImpl::JobController::ResumeMainJob, | |
| 475 ptr_factory_.GetWeakPtr()), | |
| 476 main_job_wait_time_); | |
| 477 } | |
| 478 } | |
| 479 } | |
| 480 | |
| 481 void HttpStreamFactoryImpl::JobController::OnConnectionInitialized(Job* job, | |
| 482 int rv) { | |
|
Ryan Hamilton
2016/07/12 00:21:45
can we do DCHECK(main_job_is_blocked_)?
Zhongyi Shi
2016/07/12 23:03:12
No, we couldn't. OnConnectionInitialized is called
| |
| 483 if (rv != OK && rv != ERR_SPDY_SESSION_ALREADY_EXISTS) { | |
|
Ryan Hamilton
2016/07/12 00:21:45
I think perhaps we should move this ERR_SPDY_SESSI
Zhongyi Shi
2016/07/12 23:03:12
Done.
| |
| 484 // Resume the main job as there's an error raised in connection | |
| 485 // initiation except the case where we find an existing SPDY session for | |
| 486 // this Job. | |
| 487 return MaybeResumeMainJob(job, main_job_wait_time_); | |
| 488 } | |
| 489 } | |
| 490 | |
| 491 bool HttpStreamFactoryImpl::JobController::ShouldWait(Job* job) { | |
| 492 // The alternative job never waits. | |
| 493 if (job == alternative_job_.get()) | |
| 494 return false; | |
| 495 | |
| 496 if (!main_job_is_blocked_ && main_job_wait_time_.is_zero()) | |
| 497 return false; | |
| 498 | |
| 499 if (!main_job_wait_time_.is_zero()) { | |
| 500 main_job_is_blocked_ = false; | |
| 501 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | |
| 502 FROM_HERE, | |
| 503 base::Bind(&HttpStreamFactoryImpl::JobController::ResumeMainJob, | |
| 504 ptr_factory_.GetWeakPtr()), | |
| 505 main_job_wait_time_); | |
| 506 } | |
|
Ryan Hamilton
2016/07/12 00:21:45
Should this be:
if (main_job_is_blocked_)
retur
Zhongyi Shi
2016/07/12 23:03:12
Done!
Hmmm, you are right. We should never run in
| |
| 507 return true; | |
| 508 } | |
| 509 | |
| 430 void HttpStreamFactoryImpl::JobController::SetSpdySessionKey( | 510 void HttpStreamFactoryImpl::JobController::SetSpdySessionKey( |
| 431 Job* job, | 511 Job* job, |
| 432 const SpdySessionKey& spdy_session_key) { | 512 const SpdySessionKey& spdy_session_key) { |
| 433 if (is_preconnect_ || (job_bound_ && bound_job_ != job)) | 513 if (is_preconnect_ || (job_bound_ && bound_job_ != job)) |
| 434 return; | 514 return; |
| 435 | 515 |
| 436 DCHECK(request_); | 516 DCHECK(request_); |
| 437 if (!request_->HasSpdySessionKey()) { | 517 if (!request_->HasSpdySessionKey()) { |
| 438 RequestSet& request_set = | 518 RequestSet& request_set = |
| 439 factory_->spdy_session_request_map_[spdy_session_key]; | 519 factory_->spdy_session_request_map_[spdy_session_key]; |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 469 } | 549 } |
| 470 | 550 |
| 471 const BoundNetLog* HttpStreamFactoryImpl::JobController::GetNetLog( | 551 const BoundNetLog* HttpStreamFactoryImpl::JobController::GetNetLog( |
| 472 Job* job) const { | 552 Job* job) const { |
| 473 if (is_preconnect_ || (job_bound_ && bound_job_ != job)) | 553 if (is_preconnect_ || (job_bound_ && bound_job_ != job)) |
| 474 return nullptr; | 554 return nullptr; |
| 475 DCHECK(request_); | 555 DCHECK(request_); |
| 476 return &request_->net_log(); | 556 return &request_->net_log(); |
| 477 } | 557 } |
| 478 | 558 |
| 559 void HttpStreamFactoryImpl::JobController::SetWaitTimeForMainJob( | |
| 560 const base::TimeDelta& delay) { | |
| 561 main_job_wait_time_ = delay; | |
|
Ryan Hamilton
2016/07/12 00:21:45
DCHECK(main_job_is_blocked);
Zhongyi Shi
2016/07/12 23:03:12
Nooo. This method is called by the Job without kno
| |
| 562 } | |
| 563 | |
| 479 WebSocketHandshakeStreamBase::CreateHelper* HttpStreamFactoryImpl:: | 564 WebSocketHandshakeStreamBase::CreateHelper* HttpStreamFactoryImpl:: |
| 480 JobController::websocket_handshake_stream_create_helper() { | 565 JobController::websocket_handshake_stream_create_helper() { |
| 481 DCHECK(request_); | 566 DCHECK(request_); |
| 482 return request_->websocket_handshake_stream_create_helper(); | 567 return request_->websocket_handshake_stream_create_helper(); |
| 483 } | 568 } |
| 484 | 569 |
| 485 void HttpStreamFactoryImpl::JobController::CreateJobs( | 570 void HttpStreamFactoryImpl::JobController::CreateJobs( |
| 486 const HttpRequestInfo& request_info, | 571 const HttpRequestInfo& request_info, |
| 487 RequestPriority priority, | 572 RequestPriority priority, |
| 488 const SSLConfig& server_ssl_config, | 573 const SSLConfig& server_ssl_config, |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 514 HostPortPair alternative_destination(alternative_service.host_port_pair()); | 599 HostPortPair alternative_destination(alternative_service.host_port_pair()); |
| 515 ignore_result( | 600 ignore_result( |
| 516 ApplyHostMappingRules(request_info.url, &alternative_destination)); | 601 ApplyHostMappingRules(request_info.url, &alternative_destination)); |
| 517 | 602 |
| 518 alternative_job_.reset(job_factory_->CreateJob( | 603 alternative_job_.reset(job_factory_->CreateJob( |
| 519 this, ALTERNATIVE, session_, request_info, priority, server_ssl_config, | 604 this, ALTERNATIVE, session_, request_info, priority, server_ssl_config, |
| 520 proxy_ssl_config, alternative_destination, origin_url, | 605 proxy_ssl_config, alternative_destination, origin_url, |
| 521 alternative_service, net_log.net_log())); | 606 alternative_service, net_log.net_log())); |
| 522 AttachJob(alternative_job_.get()); | 607 AttachJob(alternative_job_.get()); |
| 523 | 608 |
| 524 main_job_->WaitFor(alternative_job_.get()); | 609 main_job_is_blocked_ = true; |
| 525 // Make sure to wait until we call WaitFor(), before starting | |
| 526 // |alternative_job|, otherwise |alternative_job| will not notify |job| | |
| 527 // appropriately. | |
| 528 alternative_job_->Start(request_->stream_type()); | 610 alternative_job_->Start(request_->stream_type()); |
| 529 } | 611 } |
| 530 // Even if |alternative_job| has already finished, it will not have notified | 612 // Even if |alternative_job| has already finished, it will not have notified |
| 531 // the request yet, since we defer that to the next iteration of the | 613 // the request yet, since we defer that to the next iteration of the |
| 532 // MessageLoop, so starting |main_job_| is always safe. | 614 // MessageLoop, so starting |main_job_| is always safe. |
| 533 main_job_->Start(request_->stream_type()); | 615 main_job_->Start(request_->stream_type()); |
| 534 } | 616 } |
| 535 | 617 |
| 536 void HttpStreamFactoryImpl::JobController::AttachJob(Job* job) { | 618 void HttpStreamFactoryImpl::JobController::AttachJob(Job* job) { |
| 537 DCHECK(job); | 619 DCHECK(job); |
| (...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 812 first_alternative_service = alternative_service; | 894 first_alternative_service = alternative_service; |
| 813 } | 895 } |
| 814 | 896 |
| 815 // Ask delegate to mark QUIC as broken for the origin. | 897 // Ask delegate to mark QUIC as broken for the origin. |
| 816 if (quic_advertised && quic_all_broken && delegate != nullptr) | 898 if (quic_advertised && quic_all_broken && delegate != nullptr) |
| 817 delegate->OnQuicBroken(); | 899 delegate->OnQuicBroken(); |
| 818 | 900 |
| 819 return first_alternative_service; | 901 return first_alternative_service; |
| 820 } | 902 } |
| 821 } | 903 } |
| OLD | NEW |