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.