Chrome Shutdown

This documents shutdown steps on Windows, Mac and Linux.

On Android, the system can terminate the Chrome app at any point without running any shutdown step.

See below for how the process differs on ChromeOS.

Step 0: Profile destruction

Since M98, Chrome can destroy Profile objects separately from shutdown; on Windows and Linux, this happens in multi-profile scenarios. On macOS, it can also happen in single-profile scenarios, because Chrome lifetime is separate from browser windows.

Typically, this logic triggers when all browser windows are closed, but other things can keep a Profile alive.

~ScopedProfileKeepAlive posts a task to run RemoveKeepAliveOnUIThread. This decrements the refcount in ProfileManager, and if it hits zero then DestroyProfileWhenAppropriate is called.

ProfileDestroyer::DestroyProfileWhenAppropriate
...
ProfileManager::RemoveProfile
ProfileManager::RemoveKeepAlive
ScopedProfileKeepAlive::RemoveKeepAliveOnUIThread

Unlike regular profiles, OTR profiles are not refcounted. Instead, ~Browser checks the profile‘s browser count after removing itself. If it’s zero, it calls DestroyProfileWhenAppropriate directly.

ProfileDestroyer::DestroyProfileWhenAppropriate
Browser::~Browser

You can use ProfileManager logging to inspect a profile's keepalive state:

$ ./out/Default/chrome --enable-logging=stderr --v=0 --vmodule=profile_manager=1
[71002:259:0328/133310.430142:VERBOSE1:profile_manager.cc(1489)] AddKeepAlive(Default, kBrowserWindow). keep_alives=[kWaitingForFirstBrowserWindow (1), kBrowserWindow (1)]
[71002:259:0328/133310.430177:VERBOSE1:profile_manager.cc(1543)] ClearFirstBrowserWindowKeepAlive(Default). keep_alives=[kBrowserWindow (1)]
[71002:259:0328/133314.468135:VERBOSE1:profile_manager.cc(1489)] AddKeepAlive(Default, kExtensionUpdater). keep_alives=[kBrowserWindow (1), kExtensionUpdater (1)]
[71002:259:0328/133314.469444:VERBOSE1:profile_manager.cc(1522)] RemoveKeepAlive(Default, kExtensionUpdater). keep_alives=[kBrowserWindow (1)]
[71002:259:0328/133315.396614:VERBOSE1:profile_manager.cc(1489)] AddKeepAlive(Default, kOffTheRecordProfile). keep_alives=[kBrowserWindow (1), kOffTheRecordProfile (1)]
[71002:259:0328/133417.078148:VERBOSE1:profile_manager.cc(1522)] RemoveKeepAlive(Default, kBrowserWindow). keep_alives=[kOffTheRecordProfile (1)]
[71002:259:0328/133442.705250:VERBOSE1:profile_manager.cc(1522)] RemoveKeepAlive(Default, kOffTheRecordProfile). keep_alives=[]
[71002:259:0328/133442.705296:VERBOSE1:profile_manager.cc(1567)] Deleting profile Default

Step 1: Exiting the main loop

Shutdown starts when nothing keeps Chrome alive. Typically, this happens when all browser windows are closed, but other things can keep Chrome alive.

When nothing keeps Chrome alive, BrowserProcessImpl::Unpin asks the main thread's message loop to quit as soon as it no longer has tasks ready to run immediately.

base::RunLoop::QuitWhenIdle
…
BrowserProcessImpl::Unpin
BrowserProcessImpl::OnKeepAliveStateChanged
KeepAliveRegistry::OnKeepAliveStateChanged
KeepAliveRegistry::Unregister
ScopedKeepAlive::~ScopedKeepAlive
...
Browser::UnregisterKeepAlive
BrowserList::RemoveBrowser
Browser::~Browser

Following this request, ChromeBrowserMainParts::MainMessageLoopRun exits. Tasks posted to the main thread without a delay prior to this point are guaranteed to have run; tasks posted to the main thread after this point will never run.

Step 2: Cleaning up, after main loop exit

BrowserMainRunnerImpl::Shutdown is called on the main thread. Within that method, BrowserMainLoop::ShutdownThreadsAndCleanUp orchestrates the main shutdown steps.

ChromeBrowserMainParts::PostMainMessageLoopRun is invoked. It invokes the PostMainMessageLoopRun method of each ChromeBrowserMainExtraParts instance. This is a good place to perform shutdown steps of a component that require the IO thread, the ThreadPool or the Profile to still be available.

ChromeBrowserMainParts::PostMainMessageLoopRun also invokes BrowserProcessImpl::StartTearDown which deletes many services owned by BrowserProcessImpl (aka g_browser_process). One of these services is the ProfileManager. Deleting the ProfileManager deletes Profiles. As part of deleting a Profile, its KeyedServices are deleted, including:

  • Sync Service
  • History Service

Step 3: Joining other threads

The IO thread is joined. No IPC or Mojo can be received after this.

ThreadPool shutdown starts. At this point, no new SKIP_ON_SHUTDOWN or CONTINUE_ON_SHUTDOWN task can start running (they are deleted without running). The main thread blocks until all SKIP_ON_SHUTDOWN tasks that started running prior to ThreadPool shutdown start are complete, and all BLOCK_SHUTDOWN tasks are complete (irrespective of whether they were posted before or after ThreadPool shutdown start). When no more SKIP_ON_SHUTDOWN is running and no more BLOCK_SHUTDOWN task is queued or running, the main thread is unblocked and ThreadPool shutdown is considered complete. Note: CONTINUE_ON_SHUTDOWN tasks that started before ThreadPool shutdown may still be running.

At this point, new tasks posted to the IO thread or to the ThreadPool cannot run. It is illegal to post a BLOCK_SHUTDOWN task to the ThreadPool (enforced by a DCHECK).

Step 4: Cleaning up, after joining other threads

ChromeBrowserMainParts::PostDestroyThreads is invoked. It invokes BrowserProcessImpl::PostDestroyThreads. Since it is guaranteed that no SKIP_ON_SHUTDOWN or BLOCK_SHUTDOWN task is running at this point, it is a good place to delete objects accessed directly from these tasks.

Then, if a new Chrome executable, it is swapped with the current one (Windows-only).

upgrade_util::SwapNewChromeExeIfPresent
browser_shutdown::ShutdownPostThreadsStop
ChromeBrowserMainParts::PostDestroyThreads
content::BrowserMainLoop::ShutdownThreadsAndCleanUp
content::BrowserMainLoop::ShutdownThreadsAndCleanUp
content::BrowserMainRunnerImpl::Shutdown

ChromeOS differences

On ChromeOS, the ash browser is only supposed to exit when the user logs out.

When the user logs out, the browser sends a StopSession message to the session_manager. The session_manager then sends a SIGTERM to the main browser process to cause an exit. Once SIGTERM is received, it starts shutting down the main loop and cleaning up in the sequence described above.

Unlike other desktop platforms, the shutdown is time limited. If the browser process has not exited within a certain time frame (normally, 3 seconds), the session_manager will SIGKILL the browser process since the user is looking at a blank screen and unable to use their Chromebook until the browser exits.