
Bug: 1053445 Change-Id: If30916182adb2b9933fbd5a9bf64a8d3b6ef7ed7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3553316 Reviewed-by: David Roger <droger@chromium.org> Reviewed-by: Francois Pierre Doray <fdoray@chromium.org> Commit-Queue: Nicolas Ouellet-Payeur <nicolaso@chromium.org> Cr-Commit-Position: refs/heads/main@{#986608}
157 lines
6.9 KiB
Markdown
157 lines
6.9 KiB
Markdown
# Chrome Shutdown
|
|
|
|
[TOC]
|
|
|
|
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](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/profiles/keep_alive/profile_keep_alive_types.h).
|
|
|
|
`~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](https://source.chromium.org/chromium/chromium/src/+/main:components/keep_alive_registry/keep_alive_types.h).
|
|
|
|
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](https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/main/login_manager/README.md).
|
|
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.
|