Bug 1635451 - Minimize content processes' connections to the X server. r=jgilbert,stransky,nika
authorJed Davis <jld@mozilla.com>
Tue, 06 Jul 2021 07:42:42 +0000
changeset 584823 c49de061c1fab19f934574e959938c5500d1d080
parent 584822 c1bd0996764c49174c9169fe5550905ce5dcef88
child 584824 476ff480c877b7d396fef6e41ba91288d6050f50
push id38588
push userapavel@mozilla.com
push dateTue, 06 Jul 2021 21:42:42 +0000
treeherdermozilla-central@5b4463c3dc93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgilbert, stransky, nika
bugs1635451
milestone91.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1635451 - Minimize content processes' connections to the X server. r=jgilbert,stransky,nika This patch launches content processes with the `MOZ_HEADLESS` env var set if they're using GTK with an X11 display (and there's no other reason they'd need GTK). The goal is to avoid exhausting Xorg's default limit of 256 clients if there are many content processes due to Fission. If these conditions are met, the content process doesn't need to eagerly connect to the X server. This does not affect the sandbox policy, and content processes can still use X if needed for, e.g., WebGL. The boolean pref `dom.ipc.avoid-gtk`, set by default, controls this feature. In the future it could also be extended to minimize GTK use with Wayland displays. Note that disabling `widget.non-native-theme.enabled`, which is also enabled by default, will restore the use of X11 in all content processes even if this pref is set; the alternative is that widgets wouldn't render in that case. This change will also save some memory for now-unnecessary instances of GTK's global state, and improve content process startup time. Remove also the temp pref dom.ipc.remote-mozIcon because it cannot work anymore with the content process being headless. Differential Revision: https://phabricator.services.mozilla.com/D112197
dom/ipc/ContentParent.cpp
dom/ipc/tests/browser.ini
dom/ipc/tests/browser_very_fission.js
gfx/gl/GLContextProviderWayland.cpp
image/decoders/icon/gtk/nsIconChannel.cpp
image/test/browser/browser_mozicon_file.js
image/test/browser/browser_mozicon_file_sandbox_headless.js
image/test/browser/browser_sandbox_headless.ini
image/test/browser/head.js
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/GeckoChildProcessHost.h
modules/libpref/init/StaticPrefList.yaml
testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -274,16 +274,17 @@
 #ifdef MOZ_WIDGET_ANDROID
 #  include "AndroidBridge.h"
 #  include "mozilla/java/GeckoProcessManagerWrappers.h"
 #  include "mozilla/java/GeckoProcessTypeWrappers.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK
 #  include <gdk/gdk.h>
+#  include "mozilla/WidgetUtilsGtk.h"
 #endif
 
 #include "mozilla/RemoteSpellCheckEngineParent.h"
 
 #include "Crypto.h"
 
 #ifdef MOZ_WEBSPEECH
 #  include "mozilla/dom/SpeechSynthesisParent.h"
@@ -2540,16 +2541,25 @@ bool ContentParent::BeginSubprocessLaunc
     mSubprocess->DisableOSActivityMode();
   }
 #endif
 
   nsCString parentBuildID(mozilla::PlatformBuildID());
   extraArgs.push_back("-parentBuildID");
   extraArgs.push_back(parentBuildID.get());
 
+#ifdef MOZ_WIDGET_GTK
+  // This is X11-only pending a solution for WebGL in Wayland mode.
+  if (StaticPrefs::dom_ipc_avoid_gtk() &&
+      StaticPrefs::widget_non_native_theme_enabled() &&
+      widget::GdkIsX11Display()) {
+    mSubprocess->SetEnv("MOZ_HEADLESS", "1");
+  }
+#endif
+
   // See also ActorDealloc.
   mSelfRef = this;
   mLaunchYieldTS = TimeStamp::Now();
   return mSubprocess->AsyncLaunch(std::move(extraArgs));
 }
 
 void ContentParent::LaunchSubprocessReject() {
   NS_ERROR("failed to launch child in the parent");
--- a/dom/ipc/tests/browser.ini
+++ b/dom/ipc/tests/browser.ini
@@ -32,8 +32,11 @@ skip-if = !e10s
 support-files = file_dummy.html
 skip-if = !e10s
 [browser_bug1686194.js]
 support-files = file_dummy.html
 [browser_gc_schedule.js]
 skip-if =
   verify
   fission && os == "linux" && asan  # Bug 1713905 - new Fission platform triage
+[browser_very_fission.js]
+support-files = file_dummy.html
+run-if = widget == "gtk"
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/browser_very_fission.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This test creates a large number of content processes as a
+// regression test for bug 1635451.
+
+const TEST_PAGE =
+  "http://mochi.test:8888/browser/dom/ipc/tests/file_dummy.html";
+
+const NUM_TABS = 256;
+
+add_task(async () => {
+  let promises = [];
+  for (let i = 0; i < NUM_TABS; ++i) {
+    promises.push(
+      BrowserTestUtils.openNewForegroundTab({
+        gBrowser,
+        opening: TEST_PAGE,
+        waitForLoad: true,
+        forceNewProcess: true,
+      })
+    );
+  }
+
+  let tabs = [];
+  for (const p of promises) {
+    tabs.push(await p);
+  }
+
+  ok(true, "All of the tabs loaded");
+
+  for (const t of tabs) {
+    BrowserTestUtils.removeTab(t);
+  }
+});
--- a/gfx/gl/GLContextProviderWayland.cpp
+++ b/gfx/gl/GLContextProviderWayland.cpp
@@ -12,49 +12,59 @@
 namespace mozilla::gl {
 
 using namespace mozilla::gfx;
 using namespace mozilla::widget;
 
 static class GLContextProviderX11 sGLContextProviderX11;
 static class GLContextProviderEGL sGLContextProviderEGL;
 
+// Note that if there is no GTK display, `GdkIsX11Display` and
+// `GdkIsWaylandDisplay` will both return false.  That case can
+// currently happen only in X11 mode if the pref `dom.ipc.avoid-gtk`
+// is set (and applicable to this process).  Thus, these conditionals
+// check for the presence of Wayland rather than the absence of X11.
+//
+// In the future we'll want `dom.ipc.avoid-gtk` to also apply to
+// Wayland; at that time we'll need another way to communicate the
+// choice of window system.
+
 already_AddRefed<GLContext> GLContextProviderWayland::CreateForCompositorWidget(
     CompositorWidget* aCompositorWidget, bool aHardwareWebRender,
     bool aForceAccelerated) {
-  if (GdkIsX11Display()) {
-    return sGLContextProviderX11.CreateForCompositorWidget(
+  if (GdkIsWaylandDisplay()) {
+    return sGLContextProviderEGL.CreateForCompositorWidget(
         aCompositorWidget, aHardwareWebRender, aForceAccelerated);
   } else {
-    return sGLContextProviderEGL.CreateForCompositorWidget(
+    return sGLContextProviderX11.CreateForCompositorWidget(
         aCompositorWidget, aHardwareWebRender, aForceAccelerated);
   }
 }
 
 /*static*/
 already_AddRefed<GLContext> GLContextProviderWayland::CreateHeadless(
     const GLContextCreateDesc& desc, nsACString* const out_failureId) {
-  if (GdkIsX11Display()) {
+  if (GdkIsWaylandDisplay()) {
+    return sGLContextProviderEGL.CreateHeadless(desc, out_failureId);
+  } else {
     return sGLContextProviderX11.CreateHeadless(desc, out_failureId);
-  } else {
-    return sGLContextProviderEGL.CreateHeadless(desc, out_failureId);
   }
 }
 
 /*static*/
 GLContext* GLContextProviderWayland::GetGlobalContext() {
-  if (GdkIsX11Display()) {
+  if (GdkIsWaylandDisplay()) {
+    return sGLContextProviderEGL.GetGlobalContext();
+  } else {
     return sGLContextProviderX11.GetGlobalContext();
-  } else {
-    return sGLContextProviderEGL.GetGlobalContext();
   }
 }
 
 /*static*/
 void GLContextProviderWayland::Shutdown() {
-  if (GdkIsX11Display()) {
+  if (GdkIsWaylandDisplay()) {
+    sGLContextProviderEGL.Shutdown();
+  } else {
     sGLContextProviderX11.Shutdown();
-  } else {
-    sGLContextProviderEGL.Shutdown();
   }
 }
 
 }  // namespace mozilla::gl
--- a/image/decoders/icon/gtk/nsIconChannel.cpp
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -10,18 +10,16 @@
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/gfx/Swizzle.h"
 #include "mozilla/ipc/ByteBuf.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/StaticPrefs_dom.h"
 #include <algorithm>
 
 #include <gio/gio.h>
 
 #include <gtk/gtk.h>
 
 #include "nsMimeTypes.h"
 #include "nsIMIMEService.h"
@@ -402,18 +400,17 @@ nsresult nsIconChannel::GetIcon(nsIURI* 
   return rv;
 }
 
 nsresult nsIconChannel::Init(nsIURI* aURI) {
   nsresult rv;
   nsCOMPtr<nsIInputStream> stream;
 
   using ContentChild = mozilla::dom::ContentChild;
-  auto* contentChild = ContentChild::GetSingleton();
-  if (contentChild && mozilla::StaticPrefs::dom_ipc_remote_mozIcon()) {
+  if (auto* contentChild = ContentChild::GetSingleton()) {
     // Get the icon via IPC and translate the promise of a ByteBuf
     // into an actually-existing channel.
     RefPtr<ContentChild::GetSystemIconPromise> icon =
         contentChild->SendGetSystemIcon(aURI);
     if (!icon) {
       return NS_ERROR_UNEXPECTED;
     }
 
--- a/image/test/browser/browser_mozicon_file.js
+++ b/image/test/browser/browser_mozicon_file.js
@@ -1,24 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 add_task(async function test_mozicon_file_no_sandbox() {
   assertFileProcess();
-  assertMozIconIsRemote();
   await createMozIconInFile("txt");
   await createMozIconInFile("exe");
   await createMozIconInFile("non-existent-bidule");
 });
-
-add_task(async function test_mozicon_file_no_sandbox_no_remote() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["dom.ipc.remote-mozIcon", false]],
-  });
-  assertFileProcess();
-  assertMozIconIsNotRemote();
-  await createMozIconInFile("txt");
-  await createMozIconInFile("exe");
-  await createMozIconInFile("non-existent-bidule");
-});
--- a/image/test/browser/browser_mozicon_file_sandbox_headless.js
+++ b/image/test/browser/browser_mozicon_file_sandbox_headless.js
@@ -6,22 +6,8 @@
 
 add_task(async function test_mozicon_file_with_sandbox() {
   assertFileProcess();
   assertSandboxHeadless();
   await createMozIconInFile("txt");
   await createMozIconInFile("exe");
   await createMozIconInFile("non-existent-bidule");
 });
-
-// https://bugzilla.mozilla.org/show_bug.cgi?id=1695381#c0
-// with sandbox and no remote enabled, this is expected to fail
-add_task(async function test_mozicon_file_with_sandbox_no_remote() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["dom.ipc.remote-mozIcon", false]],
-  });
-  assertFileProcess();
-  assertSandboxHeadless();
-  assertMozIconIsNotRemote();
-  await createMozIconInFile("txt", false);
-  await createMozIconInFile("exe", false);
-  await createMozIconInFile("non-existent-bidule", false);
-});
--- a/image/test/browser/browser_sandbox_headless.ini
+++ b/image/test/browser/browser_sandbox_headless.ini
@@ -1,12 +1,8 @@
 [DEFAULT]
 support-files =
   head.js
 prefs =
   security.sandbox.content.headless=true
-skip-if =
-  (os != 'linux') # the pref is only used on linux
-  tsan # timeout on test_mozicon_file_with_sandbox_no_remote
-  asan # timeout on test_mozicon_file_with_sandbox_no_remote
-  ccov # timeout on test_mozicon_file_with_sandbox_no_remote
+skip-if = (os != 'linux') # the pref is only used on linux
 
 [browser_mozicon_file_sandbox_headless.js]
--- a/image/test/browser/head.js
+++ b/image/test/browser/head.js
@@ -37,24 +37,16 @@ function assertFileProcess() {
   // Ensure that the file content process is enabled.
   assertPrefVal("browser.tabs.remote.separateFileUriProcess", true);
 }
 
 function assertSandboxHeadless() {
   assertPrefVal("security.sandbox.content.headless", true);
 }
 
-function assertMozIconIsRemote() {
-  assertPrefVal("dom.ipc.remote-mozIcon", true);
-}
-
-function assertMozIconIsNotRemote() {
-  assertPrefVal("dom.ipc.remote-mozIcon", false);
-}
-
 function getPage() {
   let filePage = undefined;
   const { Services } = ChromeUtils.import(
     "resource://gre/modules/Services.jsm"
   );
   switch (Services.appinfo.OS) {
     case "WINNT":
       filePage = "file:///C:/";
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -598,16 +598,23 @@ class AutoCFTypeObject {
 
 // We start the unique IDs at 1 so that 0 can be used to mean that
 // a component has no unique ID assigned to it.
 uint32_t GeckoChildProcessHost::sNextUniqueID = 1;
 
 /* static */
 uint32_t GeckoChildProcessHost::GetUniqueID() { return sNextUniqueID++; }
 
+/* static */
+void GeckoChildProcessHost::SetEnv(const char* aKey, const char* aValue) {
+  MOZ_ASSERT(mLaunchOptions);
+  mLaunchOptions->env_map[ENVIRONMENT_STRING(aKey)] =
+      ENVIRONMENT_STRING(aValue);
+}
+
 void GeckoChildProcessHost::PrepareLaunch() {
   if (CrashReporter::GetEnabled()) {
     CrashReporter::OOPInit();
   }
 
 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
   SandboxLaunchPrepare(mProcessType, mLaunchOptions.get());
 #endif
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -79,16 +79,20 @@ class GeckoChildProcessHost : public Chi
   // otherwise, it could happen immediately.
   //
   // GeckoChildProcessHost instances must not be deleted except
   // through this method.
   void Destroy();
 
   static uint32_t GetUniqueID();
 
+  // Call this before launching to set an environment variable for the
+  // child process.  The arguments must be UTF-8.
+  void SetEnv(const char* aKey, const char* aValue);
+
   // Does not block.  The IPC channel may not be initialized yet, and
   // the child process may or may not have been created when this
   // method returns.
   bool AsyncLaunch(StringVector aExtraOpts = StringVector());
 
   virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0);
 
   // Block until the IPC channel for our subprocess is initialized and
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -2256,29 +2256,38 @@
 
 # How often to check for CPOW timeouts (ms). CPOWs are only timed
 # out by the hang monitor.
 - name: dom.ipc.cpow.timeout
   type: uint32_t
   value: 500
   mirror: always
 
-# Temporary pref to allow disabling remoting of MozIcon
-- name: dom.ipc.remote-mozIcon
-  type: bool
-  value: true
-  mirror: always
-
 #ifdef MOZ_ENABLE_FORKSERVER
 - name: dom.ipc.forkserver.enable
   type: bool
   value: false
   mirror: once
 #endif
 
+#ifdef MOZ_WIDGET_GTK
+#
+# Avoid the use of GTK in content processes if possible, by running
+# them in headless mode, to conserve resources (e.g., connections to
+# the X server).  See the usage in `ContentParent.cpp` for the full
+# definition of "if possible".
+#
+# This does not affect sandbox policies; content processes may still
+# dynamically connect to the display server for, e.g., WebGL.
+- name: dom.ipc.avoid-gtk
+  type: bool
+  value: true
+  mirror: always
+#endif
+
 # Whether or not to collect a paired minidump when force-killing a
 # content process.
 - name: dom.ipc.tabs.createKillHardCrashReports
   type: bool
   value: @IS_NOT_RELEASE_OR_BETA@
   mirror: once
 
 # Allow Flash async drawing mode in 64-bit release builds.
--- a/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html
+++ b/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html
@@ -29,30 +29,32 @@
 <p>blah blah blah blah</p>
 <p>blah blah blah blah</p>
 </div>
 <script class="testbody" type="text/javascript">
 const kStrictKeyPressEvents =
   SpecialPowers.getBoolPref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content");
 const kStrictKeyDownKeyUpEvents =
   SpecialPowers.getBoolPref("dom.keyboardevent.dispatch_during_composition");
-const kIsHeadless =
-  SpecialPowers.Cc["@mozilla.org/gfx/info;1"].getService(SpecialPowers.Ci.nsIGfxInfo).isHeadless;
 
 info("\nProfile::EventUtilsLoadTime: " + (loadTime - start) + "\n");
 function starttest() {
   SimpleTest.waitForFocus(
     async function () {
       SimpleTest.waitForExplicitFinish();
       var startTime = new Date();
       var check = false;
       function doCheck() {
         check = true;
       }
 
+      const kIsHeadless = await SpecialPowers.spawnChrome([], () => {
+        return Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless;
+      });
+
       if (navigator.appVersion.includes("Android")) {
         // This is the workaround for test failure on debug build.
         await SpecialPowers.pushPrefEnv({set: [["apz.zoom-to-focused-input.enabled", false]]});
       }
 
       /* test send* functions */
       $("testMouseEvent").addEventListener("click", doCheck, {once: true});
       sendMouseEvent({type:'click'}, "testMouseEvent");