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