1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156

docs / shutdown.md [blame]

# 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.