New issue
Advanced search Search tips

Issue 823567 link

Starred by 1 user

Issue metadata

Status: Fixed
Owner:
Closed: Mar 2018
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: ----
Pri: 2
Type: Bug



Sign in to add a comment

Late swapout ACK can cause an active RenderViewHost to be incorrectly marked as inactive

Project Member Reported by alex...@chromium.org, Mar 20 2018

Issue description

The following test, originally from lfg@'s https://codereview.chromium.org/2496233003/patch/380001/390004, shows how this can happen:

IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
                       RenderViewHostStaysActiveWithLateSwapoutACK) {
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));

  // Open a popup and navigate it to b.com.
  Shell* popup = OpenPopup(
      shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), "foo");

  RenderFrameHostImpl* rfh =
      static_cast<RenderFrameHostImpl*>(popup->web_contents()->GetMainFrame());
  RenderViewHostImpl* rvh = rfh->render_view_host();

  // Disable the swapout ACK and the swapout timer.
  scoped_refptr<SwapoutACKMessageFilter> filter = new SwapoutACKMessageFilter();
  rfh->GetProcess()->AddFilter(filter.get());
  rfh->DisableSwapOutTimerForTesting();

  // Navigate popup to b.com.  Because there's an opener, the RVH for a.com
  // stays around in swapped-out state.
  EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
      popup, embedded_test_server()->GetURL("b.com", "/title3.html")));
  EXPECT_FALSE(rvh->is_active());

  // Kill the b.com process.
  RenderProcessHost* b_process =
      popup->web_contents()->GetMainFrame()->GetProcess();
  RenderProcessHostWatcher crash_observer(
      b_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  b_process->Shutdown(0);
  crash_observer.Wait();

  ASSERT_TRUE(rfh->IsRenderFrameLive());
  ASSERT_FALSE(rfh->is_active());

  // Go back in the popup from b.com to a.com/title2.html.  Because the current
  // b.com RFH is dead, the new RFH is committed right away (without waiting
  // for renderer to commit), so that users don't need to look at the sad tab.
  TestNavigationObserver back_observer(popup->web_contents());
  popup->web_contents()->GetController().GoBack();

  // Pretend that the original RFH in a.com now finishes running its unload
  // handler and sends the swapout ACK.
  rfh->OnSwappedOut();

  // Wait for the new a.com navigation to finish.
  back_observer.Wait();

  // The RVH for a.com should've been reused, and it should be active.
  EXPECT_TRUE(rvh->is_active());
}

The test currently fails on the last line on ToT (@ r542543).

This might happen in an A-(1)->B-(2)->A navigation sequence where (1) has a long unload handler.  When the navigation in (1) commits, it will make the RVH for A inactive. Then the navigation in (2) will reuse the same RVH and make it active once it commits.  I think normally, the commit can't happen before the unload handler for (1) has finished running (as it's in the same process), but in this case the browser-side commit happens early, when we start the navigation, because we've also killed process B after (1).  As a result, CommitPending() might happen before the unload handler from (1) finishes, and hence the unload handler, when it's actually done, will trigger RenderFrameHostImpl::OnSwappedOut() which will incorrectly set the RVH to inactive.  Now we have an active RVH with a main frame, for which is_active() returns false.  

The timing/steps for this are very hard to get right, but I've verified it can also happen in Chrome outside of tests (had to increase unload timeout to 100s).  Running with an RVH in this state can lead to other bad things - one example is that killing and reloading the current tab will recreate the RenderView in a bad state, causing input events to no longer work.
 
Cc: -alex...@chromium.org
Owner: alex...@chromium.org
Status: Assigned (was: Available)
I'll try to fix this by having RFHI::IsSwappedOut() check whether RVH is still inactive before marking it as swapped-out.
Project Member

Comment 2 by bugdroid1@chromium.org, Mar 21 2018

The following revision refers to this bug:
  https://chromium.googlesource.com/chromium/src.git/+/4562582bc84c9696f251064c299debd9d4fbbf5a

commit 4562582bc84c9696f251064c299debd9d4fbbf5a
Author: Alex Moshchuk <alexmos@chromium.org>
Date: Wed Mar 21 19:28:27 2018

Prevent a late swapout ACK from marking a reused active RVH as inactive.

This CL fixes a bug where IsSwappedOut() always marked the RVH as
inactive and swapped-out.  This is incorrect in the case where the RVH
has been reused by another navigation that has already made it active.
The timing for that is hard to nail: usually, reusing a renderer
process for another navigation implies that the new navigation needs
to wait for a previous unload handler to finish running before a new
navigation can be started/committed.  However, if the navigation that
reuses the RVH is coming off of a cross-site sad tab page, it will
commit the pending RFH in the browser immediately, before the
navigation actually takes places in the renderer, which makes it
possible for a late swapout ACK from an unload handler to arrive after
CommitPending() has already marked the reused RVH as active.

Bug:  823567 
Change-Id: I934ad70d3bd92d03f795291d603fb584f24b6c54
Reviewed-on: https://chromium-review.googlesource.com/971304
Commit-Queue: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Lucas Gadani <lfg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544801}
[modify] https://crrev.com/4562582bc84c9696f251064c299debd9d4fbbf5a/content/browser/frame_host/render_frame_host_impl.cc
[modify] https://crrev.com/4562582bc84c9696f251064c299debd9d4fbbf5a/content/browser/frame_host/render_frame_host_impl.h
[modify] https://crrev.com/4562582bc84c9696f251064c299debd9d4fbbf5a/content/browser/site_per_process_browsertest.cc

Status: Fixed (was: Assigned)

Sign in to add a comment