New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 710953 link

Starred by 1 user

Issue metadata

Status: WontFix
Owner:
Last visit > 30 days ago
Closed: Apr 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: ----
Pri: 2
Type: Bug



Sign in to add a comment

Potential memory leak when both TCP and QUIC HttpStreamFactoryImpl::Job fail

Project Member Reported by xunji...@chromium.org, Apr 12 2017

Issue description

Not sure how often it happens in the wild, but here's a case where we will have a memory leak.

- When we start a TCP and a QUIC job and they both fail.
- Notifications are posted asynchronously.
- When JobController::OnStreamFailed(Job* job) is called the first time, it resets one of the two Jobs.
- When JobController::OnStreamFailed(Job* job) is called the second time, the controller failed to destroy the other job because (main_job_ && alternative_job_) evaluates to false.


Here's a unit test for this scenario:

TEST_F(HttpStreamFactoryImplJobControllerTest, OnStreamFailedForBothJobs2) {
  // One TCP connect and one UDP connect. They both fail.
  MockRead reads[] = {MockRead(ASYNC, ERR_FAILED, 0)};
  MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_FAILED, 1)};
  SequencedSocketData tcp_data(reads, arraysize(reads), nullptr, 0);
  tcp_data.set_connect_data(MockConnect(ASYNC, ERR_FAILED));
  SequencedSocketData udp_data(reads, arraysize(reads), writes, arraysize(writes));
  session_deps_.socket_factory->AddSocketDataProvider(&udp_data);
  session_deps_.socket_factory->AddSocketDataProvider(&tcp_data);

  ProxyConfig proxy_config;
  MockAsyncProxyResolverFactory* proxy_resolver_factory =
      new MockAsyncProxyResolverFactory(false);
  session_deps_.proxy_service.reset(
      new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
                       base::WrapUnique(proxy_resolver_factory), nullptr));

  HttpRequestInfo request_info;
  request_info.method = "GET";
  request_info.url = GURL("https://www.google.com");

  Initialize(request_info);
  url::SchemeHostPort server(request_info.url);
  AlternativeService alternative_service(kProtoQUIC, server.host(), 443);
  SetAlternativeService(request_info, alternative_service);

  request_.reset(
      job_controller_->Start(request_info, &request_delegate_, nullptr,
                             NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
                             DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
  EXPECT_TRUE(job_controller_->main_job());
  EXPECT_TRUE(job_controller_->alternative_job());

  EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(1);
  base::RunLoop().RunUntilIdle();
  VerifyBrokenAlternateProtocolMapping(request_info, false);
  EXPECT_TRUE(!job_controller_->alternative_job());
  // This fails.
  EXPECT_TRUE(!job_controller_->main_job());
  request_.reset();
  // Try again. Job is still alive when Request is gone.
  EXPECT_TRUE(!job_controller_->main_job());
}


 
Cc: zhongyi@chromium.org
Owner: xunji...@chromium.org
Status: Assigned (was: Untriaged)
Labels: -Pri-1 Pri-2
Alright, this is more or less a false alarm because if StreamRequest is destroyed, we will do another pass to clean up the jobs. 

We should make the guarantee clearer.
I think this is a false alarm, but thanks for looking into to code closely. 

I was about to say the way you test is not exactly what happens in the real world: When the second Job also reports failure to job controller, the job controller will bind the second Job to the JobController and notify the Request about the failure. The Request then will be destroyed, and the destruction will go down to the job controller to clean up everything. 

Should we close this bug or at least change the summary to make sure other folks don't get confused? 
Status: WontFix (was: Assigned)
Thanks Cherie. I agree. Given how many times I confused myself (sorry!), let's revisit this bind/orphan logic.  

Sign in to add a comment